网络流
最大流(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;
}