新学知识的模板

网络流

最大流(Dinic)

Dinic可以加入新的边,然后直接在残量网络上跑。不需要重新建图

#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
 
typedef long long ll;                 //此模板的最大流开long long
const int N = 210;                    //总点数
struct Edge { int from, to, flow; };  //flow表示这条边的容量  若容量为1,流过1之后那么容量减1变为0 如二分匹配输出方案,flow=0的边就是匹配边
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N];                     //d数组表示bfs分层的深度
int n, m, s, t; //s t是源点和汇点       cur是一个优化表示当前点到哪一个节点了,减少了很多遍历
 
void add(int from, int to, int flow)
{
	edge.push_back(Edge{from, to, flow});
	g[from].push_back(edge.size() - 1);
	edge.push_back(Edge{to, from, 0});
	g[to].push_back(edge.size() - 1);
}
 
bool bfs() //bfs标记深度优化 dfs时只能从这一层搜到下一层
{
	memset(d, 0, sizeof(d));
	queue<int> q;
	q.push(s);
	d[s] = 1;
 
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(auto x: g[u])
		{
            Edge e = edge[x];
            if(!d[e.to] && e.flow)     //记住考虑的是残量网络内的图 这条边还可以流才搜
            {
                d[e.to] = d[u] + 1;
                q.push(e.to);
            }
		}
	}
 
	return d[t];       //如果h[t]为0说明不联通,此时已经是最大流,整个算法结束
}
 
ll dfs(int u, ll in)                           //in表示当前节点流入的流量
{
	if(u == t) return in;                      //找到汇点就return
	ll out = 0;                                //out表示这个节点流出的流量
	for(int& i = cur[u]; i < g[u].size(); i++) //cur优化 注意这里引用
		{
			Edge& e = edge[g[u][i]];           //这里要引用,因为要修改
			if(d[u] + 1 == d[e.to] && e.flow)  //dfs时有的流才流
			{
			    ll f = dfs(e.to, min((ll)e.flow, in));
				e.flow -= f;
				edge[g[u][i] ^ 1].flow += f;    //反向边容量增加
				out += f; in -= f;
				if(in == 0) break;
			}
		}
	return out;
}
 
void build()                                     //建模,修改输入输出,如何加边,数据范围
{
    scanf("%d%d%d%d", &n, &m, &s, &t);
	while(m--)
	{
		int from, to, flow;
		scanf("%d%d%d", &from, &to, &flow);
		add(from, to, flow);                    //加边
	}
}
 
int main()
{
    build();
 
	ll ans = 0;
	while(bfs())
	{
	    memset(cur, 0, sizeof(cur));
        ans += dfs(s, 1e18);
	}
	printf("%lld\n", ans);
 
	return 0;
}

最小费用最大流

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 5e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], n, m, s, t;            //f[i]表示点i流入的流量
ll f[N];
                                                     //p数组用来回溯
void add(int from, int to, int flow, int cost)
{
	edge.push_back(Edge{from, to, flow, cost});
	g[from].push_back(edge.size() - 1);
	edge.push_back(Edge{to, from, 0, -cost});
	g[to].push_back(edge.size() - 1);
}
 
bool spfa()
{
    memset(vis, 0, sizeof(vis));
    memset(d, 0x3f, sizeof d);
    queue<int> q; q.push(s);
    d[s] = 0; f[s] = 1e18;
    vis[s] = 1;
 
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		for(auto x: g[u])
		{
			Edge e = edge[x];
			int v = e.to;
			if(e.flow && d[v] > d[u] + e.cost)
			{
				d[v] = d[u] + e.cost;
				p[v] = x;
				f[v] = min(f[u], (ll)e.flow);
				if(!vis[v]) { vis[v] = 1; q.push(v); }
			}
		}
	}
 
	return d[t] < 1e9;              //最后联通
}
 
void build() //建模
{
    scanf("%d%d%d%d", &n, &m, &s, &t);
	while(m--)
    {
        int from, to, flow, cost;
        scanf("%d%d%d%d", &from, &to, &flow, &cost);
        add(from, to, flow, cost);
    }
}
 
int main()
{
    build();
 
	ll maxflow = 0, mincost = 0;
	while(spfa())                                //bfs改成spfa 每次找一条花费最小的增广路增广路
    {                                            //之后不需要dfs 因为只有一条增广路
        maxflow += f[t];                         //更新答案
        mincost += 1LL * f[t] * d[t];
        for(int u = t; u != s; u = edge[p[u]].from)
        {
            edge[p[u]].flow -= f[t];            //找到回溯后修改容量
            edge[p[u] ^ 1].flow += f[t];
        }
    }
    printf("%lld %lld\n", maxflow, mincost);
 
	return 0;
}

结论

一.有向图最小路径覆盖 = n - 最大匹配 注意这里的是n不是2*n

一个点仅在一条路径上

匈牙利直接连边,Dinic要拆成左边的边和右边的点

二.最小割 = 最大流

如方格取数问题,不能选相邻点,选一些点使得权值和最大

根据坐标之和的奇偶性建立二分图 然后源点汇点连权值

正解是逆向思维,假设全都选,看怎么删除使得不互斥,这样子就容易很多

由“删除”可以想到最小割,刚好删除的最小就是权值和最大

也就是说要构建一个图,使得删除一条边就代表不选这个点

这时要利用坐标之和的奇偶性来划成二分图,这是一个很巧妙的思路

相邻的点坐标之和奇偶性一定是不同的,所以可以把坐标之和为奇数的放左边,偶数的放右边,互斥的点连边

因为坐标之和奇偶性相同的不一定不互斥,所以这是一个二分图

然后源点向左边的点连边,容量为点的权值,右边的点向汇点连边,同样容量为点的权值

中间互斥的点连边,容量为无穷大,代表不割。

当实现最小割的时候,源点和汇点不联通,这时一定是没有互斥的点的

如果由互斥的点,那么它们都选,它们之间的边不可能割掉,这样源点汇点就联通了。

最后最小割=最大流。
 

数据结构

主席树(区间第k大)

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 2e5 + 10;
int s[N << 5] , root[N << 5], ls[N << 5], rs[N << 5]; //空间要开大一些,直接左移5位
int a[N], lsh[N], n, m, cnt, len;
 
void build(int& k, int l, int r) //注意动态开点。先建立第0个版本的空树
{
    k = ++cnt;
    if(l == r) return;
    int m = l + r >> 1;
    build(ls[k], l, m);
    build(rs[k], m + 1, r);
}
 
void add(int& k, int pre, int x, int l, int r) //新建和查询的时候要有2个根节点,当前的和历史的 
{
    k = ++cnt;
    ls[k] = ls[pre]; rs[k] = rs[pre]; s[k] = s[pre] + 1; //复制前一个版本线段树的信息
    if(l == r) return;  //到叶子节点就返回
    int m = l + r >> 1;
    if(x <= m) add(ls[k], ls[pre], x, l, m); //跳的时候当前和之前的都要跳 
    else add(rs[k], rs[pre], x, m + 1, r);
}
 
int query(int pre, int now, int l, int r, int num)
{
    if(l == r) return l;
    int m = l + r >> 1, x = s[ls[now]] - s[ls[pre]]; //得到左区间内数的个数
    if(x >= num) return query(ls[pre], ls[now], l, m, num);
    else return query(rs[pre], rs[now], m + 1, r, num - x); //注意这里num - x
}
 
void init() //离散化
{
    sort(lsh + 1, lsh + n + 1);
    len = unique(lsh + 1, lsh + n + 1) - lsh - 1; //多减一
    _for(i, 1, n) a[i] = lower_bound(lsh + 1, lsh + len + 1, a[i]) - lsh;
}
 
int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n)
    {
        scanf("%d", &a[i]);
        lsh[i] = a[i];
    }
 
    init();
    build(root[0], 1, len); //注意这里右端点是离散化后的值,不是n
    _for(i, 1, n) add(root[i], root[i - 1], a[i], 1, len); //建立第i版本的线段树,插入点
 
    while(m--)
    {
        int l, r, x;
        scanf("%d%d%d", &l, &r, &x);
        printf("%d\n", lsh[query(root[l - 1], root[r], 1, len, x)]); //注意是l - 1
    }
 
	return 0;
}

可持久化Trie

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 6e5 + 10;
int t[N * 30][2], a[N], s[N], root[N];
int vis[N * 30], cnt, n, m;
 
void add(int now, int pre, int x) //建立新树注意复制前面的信息,在两颗树上跑
{
    int p = now;
    for(int i = 31; i >= 0; i--)
    {
        int idx = (x >> i) & 1;
        t[p][idx] = ++cnt;      //这里不用判断,一定是新建节点
        t[p][idx ^ 1] = t[pre][idx ^ 1]; //复制前一颗树的信息
        p = t[p][idx]; pre = t[pre][idx];
        vis[p] = vis[pre] + 1; //根节点不用处理sum 因为询问的时候用不到。sum存当前这个节点访问过的次数
    }
}
 
int query(int now, int pre, int x)
{
    int p = now, res = 0;
    for(int i = 31; i >= 0; i--)
    {
        int idx = (x >> i) & 1;
        if(vis[t[p][idx ^ 1]] > vis[t[pre][idx ^ 1]]) //作差来判断是否存在。其他是一样的
        {
            res |= 1 << i;                            
            p = t[p][idx ^ 1]; pre = t[pre][idx ^ 1];
        }
        else p = t[p][idx], pre = t[pre][idx]; //在两颗Trie树上跑,相减得当前得Trie
    }
    return res;
}
 
int main()
{
    add(root[0] = ++cnt, 0, 0); //按照公式s[0] = 0的,这个点要加入,否则会WA
    scanf("%d%d", &n, &m);      //一般来说也会建一个空树
    _for(i, 1, n)
    {
        scanf("%d", &a[i]);
        s[i] = s[i - 1] ^ a[i];
        add(root[i] = ++cnt, root[i - 1], s[i]); //给root分配一个新编号
    }
 
    while(m--)
    {
        char op[5];
        scanf("%s", op);
        if(op[0] == 'A')
        {
            int x; scanf("%d", &x);
            a[++n] = x;
            s[n] = a[n] ^ s[n - 1];
            add(root[n] = ++cnt, root[n - 1], s[n]);
        }
        else
        {
            int l, r, x;
            scanf("%d%d%d", &l, &r, &x);
            l--; r--;
            if(l == 0) printf("%d\n", query(root[r], 0, s[n] ^ x));
            else printf("%d\n", query(root[r], root[l - 1], s[n] ^ x));
        }
    }
 
	return 0;
}

二维树状数组

#include <bits/stdc++.h>
#define rep(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;
 
const int MAXN = 1e3;
int f[MAXN][MAXN], n, m, q;
 
int lowbit(int x) { return x & (-x); }
 
void add(int x, int y, int p)
{
    for(; x <= n; x += lowbit(x))
        for(int i = y; i <= m; i += lowbit(i))
            f[x][i] += p;
}
 
int s(int x, int y)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        for(int i = y; i; i -= lowbit(i))
            res += f[x][i];
    return res;
}
 
int sum(int x1, int y1, int x2, int y2)
{
    return s(x2, y2) - s(x2, y1 - 1) - s(x1 - 1, y2) + s(x1 - 1, y1 - 1);
}
 
int main()
{
	scanf("%d%d%d", &n, &m, &q);
	_for(i, 1, n)
		_for(j, 1, m)
		{
			int x; scanf("%d", &x);
			add(i, j, x);
		}
 
	while(q--)
	{
		int op, x1, y1, x2, y2, p;
		scanf("%d", &op);
		if(op == 1) scanf("%d%d%d", &x1, &y1, &p), add(x1, y1, p);
		else
		{
			scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
			printf("%d\n", sum(x1, y1, x2, y2));
		}
	}
	return 0;
}

莫队

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
struct query
{
	int l, r, id, bl;
}q[N];
int a[N], cnt[N], ans[N], n, m, sum;

bool cmp(query a, query b)
{
	if(a.bl != b.bl) return a.bl < b.bl;
	if(a.bl & 1) return a.r < b.r; //奇偶优化 防卡
	return a.r > b.r;
}

void add(int x)
{
	if(++cnt[a[x]] == 1) sum++; //这里是数不是下标
}

void erase(int x)
{
	if(--cnt[a[x]] == 0) sum--; //cnt数组记录每个数出现了多少次
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%d", &a[i]);

	int block_size = sqrt(n);
	_for(i, 1, m)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		q[i] = {l, r, i, l / block_size};
	}

	sort(q + 1, q + m + 1, cmp);

	int l = 1, r = 0;
	_for(i, 1, m)
	{
		int ll = q[i].l, rr = q[i].r;
		while(l < ll) erase(l++);   //erase先删除再移动
		while(l > ll) add(--l);    //add先移动再加 因为原来已经操作过了
		while(r > rr) erase(r--);
		while(r < rr) add(++r);
		ans[q[i].id] = (sum == (rr - ll + 1)); //这样记录答案不用再排序一次
	}
	_for(i, 1, m) puts(ans[i] ? "Yes" : "No");

	return 0;
}

分块

挺暴力的,这是个根号n的算法

本质就是每次处理时对一个块统一处理来节省时间

缺点是复杂度比较大,根号n,优点是更为灵活,可以维护更多复杂的区间修改和查询

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 1e5 + 10;
 
ll a[N], mark[N], sum[N];
int st[N], ed[N], bl[N], n, m, block;
 
void init()
{
    block = sqrt(n); //块的个数
    _for(i, 1, block)
    {
        st[i] = n / block * (i - 1) + 1;
        ed[i] = n / block * i;
    }
    ed[block] = n;
 
    _for(i, 1, block)
        _for(j, st[i], ed[i])
            bl[j] = i;
}
 
int main()
{
    scanf("%d%d", &n, &m);
    init();
 
    _for(i, 1, n)
    {
        scanf("%lld", &a[i]);
        sum[bl[i]] += a[i];
    }
 
    while(m--)
    {
        int op, x, y, k;
        scanf("%d", &op);
        if(op == 1)
        {
            scanf("%d%d%d", &x, &y, &k);
            if(bl[x] == bl[y]) _for(i, x, y) a[i] += k, sum[bl[i]] += k;
            else
            {
                _for(i, x, ed[bl[x]]) a[i] += k, sum[bl[i]] += k;
                _for(i, st[bl[y]], y) a[i] += k, sum[bl[i]] += k;
                _for(i, bl[x] + 1, bl[y] - 1) mark[i] += k, sum[i] += k * (ed[i] - st[i] + 1);
            }
        }
        else
        {
            scanf("%d%d", &x, &y);
            ll ans = 0;
            if(bl[x] == bl[y]) _for(i, x, y) ans += a[i] + mark[bl[x]];
            else
            {
                _for(i, x, ed[bl[x]]) ans += a[i] + mark[bl[x]];
                _for(i, st[bl[y]], y) ans += a[i] + mark[bl[y]];
                _for(i, bl[x] + 1, bl[y] - 1) ans += sum[i];
            }
            printf("%lld\n", ans);
        }
    }
 
	return 0;
}

单调栈

找每个数右边比大的且离它最近的数的下标

首先清楚单调栈可以用来干嘛

单调栈能O(n)时间处理出每个数离它最近的比他大/小的元素

可以处理出以一个数为最大/小值的最长区间

这道题就是要处理出每个数在右边离它最近的比它大的元素
暴力就n方,每次都往后扫。扫到第一个大于它的数

我们考虑倒着扫,依然是n方的

但是我们能否利用之前的结果呢

可以发现如果x 在 y 的右侧,x还小于等于 y 那么x是永远不可能为答案的

有点像单调队列。

怎么表示这个下标的大小的关系呢?用栈。越靠近栈顶的元素是离当前的数最近的

把上面那个排除条件放到栈里,就是更靠近栈顶又更小的元素要删除

可以发现这样删除之后,就是一个单调的栈,栈顶最小

做的过程中其实依然是暴力,只是说那些不可能为答案的点删掉了,之后的暴力就不会浪费这个时间检查这个点了

因此每次对于当前元素,先维护栈的单调性,比当前元素小于等于的弹出,然后这时栈顶就是大于它的,就是答案。然后再加入当前元素

注意栈里面存的是下标,比较的时候是比较元素,要区分

总结起来就是维护一个单调的栈,加入元素的时候要弹出一些点来维护单调性,弹完后用这时的栈顶来统计答案

​
    int l = 1, r = 0 / 1;  //r = 0表示先加入再转移 r = 1表示先转移再加入(默认队列初始有一个0) 也可以直接先初始化第一个点
    _for(i, 1, n)
    {
        if( ... > q[l] ) l++;    //超出范围的出队
        
        //转移1 转移的范围不包括当前的i
        
        while(l <= r && val(a[r]) >= a[i]) r--; //保证单调性
        q[++r] = i;
        
        //转移2 转移的范围包括当前的i
    }

​

动态规划

数位dp

数位dp无非是问一个区间内有多少个符合题目条件的数

每个数都有个值,如果问个数则值为1,问其他的看题目

ll dfs(int pos,int pre,int st,……,int lead,int limit)//记搜
{
    if(pos>len) return st;//剪枝
    if((dp[pos][pre][st]……[……]!=-1&&(!limit)&&(!lead))) return dp[pos][pre][st]……[……];//记录当前值
    ll ret=0;//暂时记录当前方案数
    int res=limit?a[len-pos+1]:9;//res当前位能取到的最大值
    for(int i=0;i<=res;i++)
    {
        //有前导0并且当前位也是前导0
        if((!i)&&lead) ret+=dfs(……,……,……,i==res&&limit);
        //有前导0但当前位不是前导0,当前位就是最高位
        else if(i&&lead) ret+=dfs(……,……,……,i==res&&limit); 
        else if(根据题意而定的判断) ret+=dfs(……,……,……,i==res&&limit);
    }
    if(!limit&&!lead) dp[pos][pre][st]……[……]=ret;//当前状态方案数记录
    return ret;
}
ll part(ll x)//把数按位拆分
{
    len=0;
    while(x) a[++len]=x%10,x/=10;
    memset(dp,-1,sizeof dp);//初始化-1(因为有可能某些情况下的方案数是0)
    return dfs(……,……,……,……);//进入记搜
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld",&l,&r);
        if(l) printf("%lld",part(r)-part(l-1));//[l,r](l!=0)
        else printf("%lld",part(r)-part(l));//从0开始要特判
    }
    return 0;
}

单调队列优化dp

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 3e6 + 10;
int a[N], s[N], ans[N], top, n;
 
int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);
 
    for(int i = n; i >= 1; i--)
    {
        while(top && a[i] >= a[s[top]]) top--;
        ans[i] = !top ? 0 : s[top];
        s[++top] = i;
    }
    _for(i, 1, n) printf("%d ", ans[i]); puts("");
 
	return 0;
}

字符串

哈希

处理过程类似131进制数

哈希有三种方法,自然溢出,单哈希,双哈希

自然溢出就是利用ull的自动取模,单哈希就是模一个质数如1e9 + 7

双哈希就是模两个质数如1e9 + 7 1e9 + 9

自然溢出和单哈希都可能被出题人卡,但是双哈希就很安全

以下是自然溢出

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
map<ull, int> mp;
ull Hash[N], p[N];
string s[N];
int n;
 
ull get_hash(string s)
{
    ull res = 0;
    int len = s.size();
    rep(i, 0, len)
        res = res * base + s[i];
    return res;
}
 
ull cut(int l, int r)   //计算某一个子串的哈希值,记住公式
{
    if(l - 1 < 0) return Hash[r];                  //注意负数的情况
    return Hash[r] - Hash[l - 1] * p[r - l + 1];
}
 
int main()
{
    p[0] = 1;
    _for(i, 1, 4e5) p[i] = p[i - 1] * base; //初始化p数组 表示base的p次方 cut用到
 
    ll ans = 0;
    scanf("%d", &n);
    _for(i, 1, n)
    {
        cin >> s[i];
        ll t = get_hash(s[i]);
        if(mp[t]) ans += mp[t];
        mp[t]++;
    }
 
    _for(i, 1, n)
    {
        string t = s[i];
        int len = t.size();
        Hash[0] = t[0];
        rep(i, 1, len) Hash[i] = Hash[i - 1] * base + t[i];
 
        _for(k, 1, len / 2)
            if(cut(0, k - 1) == cut(len - k, len - 1))
                ans += mp[cut(k, len - k - 1)];
    }
    printf("%lld\n", ans);
 
	return 0;
}

双哈希

#include <bits/stdc++.h>
#define pa pair<ull, ull>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
const int mod1 = 1e9 + 7;
const int mod2 = 998244353;
 
map<pa, int> mp;
ull Hash[N][2], p[N][2];
string s[N];
int n;
 
pa get_hash(string s)
{
    ull res1 = 0, res2 = 0;
    int len = s.size();
    rep(i, 0, len)
    {
        res1 = (res1 * base % mod1 + s[i]) % mod1;
        res2 = (res2 * base % mod2 + s[i]) % mod2;
    }
    return make_pair(res1, res2);
}
 
pa cut(int l, int r)   //计算某一个子串的哈希值,记住公式
{
    if(l - 1 < 0) return make_pair(Hash[r][0], Hash[r][1]);
    ull res1 = (Hash[r][0] - Hash[l - 1][0] * p[r - l + 1][0] % mod1 + mod1) % mod1;
    ull res2 = (Hash[r][1] - Hash[l - 1][1] * p[r - l + 1][1] % mod2 + mod2) % mod2;
    return make_pair(res1, res2);
}
 
int main()
{
    p[0][0] = p[0][1] = 1;
    _for(i, 1, 4e5)
    {
        p[i][0] = p[i - 1][0] * base % mod1;
        p[i][1] = p[i - 1][1] * base % mod2;
    }
 
    ll ans = 0;
    scanf("%d", &n);
    _for(i, 1, n)
    {
        cin >> s[i];
        pa t = get_hash(s[i]);
        if(mp[t]) ans += mp[t];
        mp[t]++;
    }
 
    _for(i, 1, n)
    {
        string t = s[i];
        int len = t.size();
        Hash[0][0] = Hash[0][1] = t[0];
        rep(i, 1, len)
        {
            Hash[i][0] = (Hash[i - 1][0] * base % mod1 + t[i]) % mod1;
            Hash[i][1] = (Hash[i - 1][1] * base % mod2 + t[i]) % mod2;
        }
        _for(k, 1, len / 2)
            if(cut(0, k - 1) == cut(len - k, len - 1))
                ans += mp[cut(k, len - k - 1)];
    }
    printf("%lld\n", ans);
 
	return 0;
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值