#include #include using namespace std; #define rep(i, n) for (int i = 0; i < (int)(n); i++) #define length size() #define int long long #define ll long long constexpr ll inf = 1001001001001001001ll; constexpr ll mod = /* 1000000007*/ 998244353; constexpr double pi = 3.141592653589793; //小数出力 //cout << std::setprecision(12); //imos void to_ruiseki(vector &vec){ for(int i=1;i<(int)vec.size();i++){ vec[i] += vec[i-1]; } } void to_2druiseki(vector> &vec){ rep(i,vec.size()){ rep(j,vec[0].size()){ int s = 0; if(i!=0) s += vec[i-1][j]; if(j!=0) s += vec[i][j-1]; if(i!=0 && j!=0) s -= vec[i-1][j-1]; s += vec[i][j]; vec[i][j] = s; } } } //modpow(a,n,mod) int modpow(int a, int n, int mod) { long long res = 1; while (n > 0) { if (n & 1) res = res * a % mod; a = a * a % mod; n >>= 1; } return res; } //素因数分解 vector> p_fact(int N) { vector> res; for (int a = 2; a * a <= N; ++a) { if (N % a != 0) continue; int ex = 0; // 指数 // 割れる限り割り続ける while (N % a == 0) { ++ex; N /= a; } // その結果を push res.push_back({a, ex}); } // 最後に残った数について if (N != 1) res.push_back({N, 1}); return res; } //ctoi int ctoi(const char c){ switch(c){ case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; default : throw runtime_error("hoge"); } } //max template< typename T1, typename T2 > inline bool chmax(T1 &a, T2 b) { return a < b && (a = b, true); } //min template< typename T1, typename T2 > inline bool chmin(T1 &a, T2 b) { return a > b && (a = b, true); } //print void print() { cout << '\n'; } template void print(const T &t) { cout << t << '\n'; } template void print(const Head &head, const Tail &... tail) { cout << head << ' '; print(tail...); } //join template string join(vector &vec ,const string &sp){ int si = vec.length; if(si==0){ return ""; }else{ stringstream ss; rep(i,si-1){ ss << vec[i] << sp; } ss << vec[si - 1]; return ss.str(); } } //木の直径 int tree_diameter(vector> &vec){ //0-indexed queue> que; vector is_searched(vec.size(),false); que.push(make_pair(0,0)); int max_d = 0; int max_e = 0; while(!que.empty()){ int e = que.front().first; int distance = que.front().second; is_searched[e] = true; if(max_d> &vec,int st){ queue> que; vector is_searched(vec.size(),false); is_searched[st] = true; que.push(make_pair(st,0)); int max_d = 0; int max_e = 0; while(!que.empty()){ int e = que.front().first; int distance = que.front().second; if(max_d par, rank, siz; // 構造体の初期化 UnionFind(int n) : par(n,-1), rank(n,0), siz(n,1) { } // 根を求める int root(int x) { if (par[x]==-1) return x; // x が根の場合は x を返す else return par[x] = root(par[x]); // 経路圧縮 } // x と y が同じグループに属するか (= 根が一致するか) bool issame(int x, int y) { return root(x)==root(y); } // x を含むグループと y を含むグループを併合する bool unite(int x, int y) { int rx = root(x), ry = root(y); // x 側と y 側の根を取得する if (rx==ry) return false; // すでに同じグループのときは何もしない // union by rank if (rank[rx] > prime_factorize(long long N) { vector > res; for (long long a = 2; a * a <= N; ++a) { if (N % a != 0) continue; long long ex = 0; // 指数 // 割れる限り割り続ける while (N % a == 0) { ++ex; N /= a; } // その結果を push res.push_back({a, ex}); } // 最後に残った数について if (N != 1) res.push_back({N, 1}); return res; } int e(){ return inf; } int op(int a,int b){ return min(a,b); } int h,w,a; void t_print(vector vec){ rep(i,h){ rep(j,w){ cout << vec[i*h+j] << " "; } cout << endl; } cout << endl; } signed main(void){ string s; cin >> s; vector> vec(26,vector(0)); rep(i,s.size()){ vec[s[i]-65].push_back(i); } int ans = 0; rep(i,26){ if(vec[i].size()<=1) continue; // print(join(vec[i]," ")); rep(j,vec[i].size()){ if(j==0) continue; ans += max(0LL,(int)(j*(s.size()-vec[i][j]-vec[i].size()+j))); // print(ans); } } print(ans); }