点对之间不同的最小割至多有种
求一遍最小割,然后继续分治处理S\T集合
ZJOI 2011 最小割
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using std::fill;
using std::copy;
const int MAXN=150+11,MAXM=3000+11,INF=INT_MAX;
struct Edge *Edge_Pool,*Edge_Me;
struct Edge {
int to,cap,v;
Edge *pre,*rev;
Edge(int to,int cap,Edge *pre):to(to),cap(cap),v(0),pre(pre) {}
void *operator new(size_t) {
return Edge_Me++;
}
}*G[MAXN]={Edge_Pool=Edge_Me=alloc(Edge,MAXM<<1)};int n;
void Reset() {
for(int pos=1;pos<=n;++pos)
for(Edge *e=G[pos];e;e=e->pre)
e->v=0;
}
namespace isap {
Edge *pree[MAXN],*arc[MAXN];
int num[MAXN],d[MAXN];
int Aug(int t) {
int minf=INF;
for(Edge *e=pree[t];e;e=pree[e->rev->to])
chkmn(minf,e->cap-e->v);
for(Edge *e=pree[t];e;e=pree[e->rev->to]) {
e->v+=minf;
e->rev->v-=minf;
}
return minf;
}
int ISAP(int s,int t) {
pree[s]=0;
fill(d+1,d+1+n,0);
fill(num+1,num+1+n,0);
copy(G+1,G+1+n,arc+1);
num[0]=n;
int flow=0;
for(int cp=s;d[s]<n;cp=cp==t?(flow+=Aug(t),s):cp) {
bool adv=0;
for(Edge *&e=arc[cp];e;e=e->pre)
if(e->cap>e->v && d[e->to]+1==d[cp]) {
adv=1;
pree[cp=e->to]=e;
break;
}
if(!adv) {
if(!--num[d[cp]])
break;
d[cp]=n;
for(Edge *e=(arc[cp]=G[cp]);e;e=e->pre)
if(e->cap>e->v) chkmn(d[cp],d[e->to]+1);
++num[d[cp]];
if(cp!=s) cp=pree[cp]->rev->to;
}
}
return flow;
}
}using isap::ISAP;
void Adde(int f,int t,int cap) {
G[f]=new Edge(t,cap,G[f]);
G[t]=new Edge(f,cap,G[t]);
G[f]->rev=G[t];G[t]->rev=G[f];
}
int minCut[MAXN][MAXN],set[MAXN];
bool ud[MAXN];
void DFS(int pos) {
ud[pos]=1;
for(Edge *e=G[pos];e;e=e->pre)
if(e->cap>e->v && !ud[e->to])
DFS(e->to);
}
void DC(int l,int r) {
if(l==r) return;
Reset();
int flow=ISAP(set[l],set[r]);
fill(ud+1,ud+1+n,0);
DFS(set[l]);
for(int i=1;i<=n;++i) if(ud[i])
for(int j=1;j<=n;++j) if(!ud[j])
minCut[j][i]=(chkmn(minCut[i][j],flow),minCut[i][j]);
static int pset[MAXN];
int pl=l,pr=r;
for(int i=l;i<=r;++i)
pset[ud[set[i]]?pl++:pr--]=set[i];
copy(pset+l,pset+r+1,set+l);
DC(l,pl-1);DC(pr+1,r);
}
void Work() {
Edge_Me=Edge_Pool;
memset(G,0,sizeof(G));
int m,Q;is>>n>>m;
for(int i=1;i<=m;++i) {
int u,v,c;is>>u>>v>>c;
Adde(u,v,c);
}
for(int i=1;i<=n;++i) {
set[i]=i;
fill(minCut[i]+1,minCut[i]+1+n,INF);
}
DC(1,n);
is>>Q;
for(int loop=1;loop<=Q;++loop) {
int x;is>>x;
int Ans=0;
for(int i=1;i<n;++i)
for(int j=i+1;j<=n;++j)
Ans+=minCut[i][j]<=x;
os<<Ans<<'\n';
}
}
int main() {
// freopen("input","r",stdin);
int T;is>>T;
while(T--) {
Work();
if(T) os<<'\n';
}
return 0;
}
BZOJ 4538 网络
Link
Solution
因为询问的是不覆盖一个点的路径 那么就把修改也转化成补集,可以通过树剖DFS序解决 感觉并不是很难想,但就是想不到。。树剖DFS序看起来有无限可能。
Code
1 | #include "lucida" |
BZOJ 2877 膜幻棋盘
Link
Sengxian大神给出的做法,说实话,确实比Po姐的简洁 虽然Po姐的做法非常直观,但是修改的操作的讨论太繁杂 本来想了一种自己的做法,可以按照Po姐的办法差分而又避免繁杂的讨论,写出来之后怎么也调不对 只好又去研究了一番Sengxian的题解,感觉非常喵
Solution
注意到的主要事实就是gcd(a,b)=gcd(a,b−a),由此就可以差分来避免区间修改。一维的做法扩展一下,最后就可以推出一个非常优美的二维做法。 Po姐的第二维可变的二维数组写法非常别致啊。。 然后细节比较多,要注意一维和二维线段树查询的边界 改成Sengxian的做法之后,一开始WA了。把二维线段树改成暴力维护矩阵,WA的一样。。 搞了一会儿没办法,把一维线段树也改成暴力了,然后就对了。。 究其原因,是因为一维线段树的边界没有考虑完全,然而一维线段树在二维线段树里是对的,是因为二维线段树的边界判全了。。 这说明,暴露在外直接面向全局的函数一定要判好边界等等特殊情况,被套在里面对了,不代表确实是对的。
Code
1 | #include "lucida" |
BZOJ 4537 LCM
Link
有一种智障叫做看到了简单路径的定义就觉得需要求简单路径 有一种被欺骗叫做写挂复杂度而心甘情愿地卡了一晚上常数
Solution
所以说,暴力并查集+线段树+O(n)扫可以得到45分。。
正解是非常鬼畜的
把边按照a排序,分成√m块
Code
1 | #include "lucida" |
BZOJ 4556 字符串
Link
Solution
如果没有S[a:b]中b端点的限制,那就是直接把prefix[a∼b]和prefix[c]的取最值再和d−c+1。取min。这样的话,需要在rank[]上,查询rank[c]在区间[a,b]中的前驱和后继。可以树套树或者可持久化 加了b的限制,那就是一部分会被截掉一段,可能本来是最长的,截掉之后就不是答案了。 然后通过某些神奇思考得到了二分的办法:对于答案mid,只会出现在[a,b−mid+1]。这样的话加一个二分答案,就巧妙地避免了限制的干扰,可以直接求rank[c]的前驱后继再$height[]$了。 另外,主席树的前驱后继似乎不好像平衡树那样直接走一遍,因为在上面不知道下面的某些点是否存在。
Tips
写出了形式化的条件,但没有想到二分答案的处理策略 应该是想一下去掉限制的想出主席树,再加上限制更容易得到二分答案.
Code
1 | #include "lucida" |
BZOJ 2084 Antisymmetry
Link
Solution
反对称串长度一定为偶数
反对称串反掉一半之后一定是回文串
于是就试着求一下每个点左边的串反转之后,这个点的回文半径
回文串?于是考虑Manacher
Manacher的主要条件是
A回文,B回文,那么C回文
现在考虑一下能不能直接套
A[1:2A.length()]RA[2A.length()+1:A.length()]回文
B[1:2B.length()]RB[2B.length()+1:B.length()]回文
是可以得到右边的C回文的
所以直接套Manacher即可,每次把真字符取反;在分隔符处更新maxRight,统计答案。
至于为什么在分隔符处更新maxRight我也不知道,,只是如果每次都更新确实会多算一些不合法的
Code
1 | #include "lucida" |
BZOJ 2342 双倍回文
Link
Solution
先
O(n2)的枚举策略是从一个#开始向两边扩展,在扩展过程中如果两个指针位置的回文串都足够长,就更新答案,直到l,r的距离超过了以i为中心的回文半径。足够长,指l+rad[l]−1≥i≥r−rad[r]+1
做了什么无用功呢?如果从左往右找,只要找到第一个l+rad[l]−1≥i的点,那就一定找到了以i为中心的最长双倍回文,因为右指针的回文串与左指针的在距离不超过i的回文半径的时候指向的字符串是对称的。
那么现在要解决的就是如何快速找到最靠左的l使得l≥i−2rad[i],l+rad[l]−1≥i
然后这个是有单调性的:l+rad[l]−1<i⇒l+rad[l]−1<i+1
所以就可以考虑用一个维护单调递增的数据结构来优化
应该可以用单调队列?似乎不能,因为rad[]并不单调
但是orz Stilwell
很神奇地用了并查集
复杂度O(nlogn)?
Code
1 | #include "lucida" |
BZOJ 2839 集合计数
Link
发现自己对容斥的理解还处于小学的水平,补点题找找感觉
Solution
首先这是求交集,被我当成并集想了大半天2333 求交集的话,那肯定是选k个当交集,选出所有的集合剩下部分的交集为∅ 设f(n,i)表示从n个元素的集合中选出交集至少有i个元素的子集的方案数,考虑容斥 那么最后的答案就是 Cnk(i=0∑n−k(−1)if(n−k,i)) 应该是比较好懂的。。 又显然f(n,k)=Cn−ki(22n−k−1),−1是因为必须选至少一个
Code
1 | #include "lucida" |
BZOJ 3589 动态树
Link
感觉这个“动态树”的脑洞开的挺不错的,,至少比“矩阵乘法(给你一个矩阵,不用做矩阵乘法,但需要询问矩阵内第K小的值)”好多了2333
Solution
求树链的交集的做法应该还是挺有意思的。。 虽然这个题用容斥只是为了容斥而容斥,,可以直接用线段树计算被标记的节点的和 但是为了写一下树链的交,写了容斥的写法
Code
1 | #include "lucida" |
BZOJ 2121 字符串游戏
Link
我只想说:是谁想出的这么**的DP?!
Solution
- 决定一个区间能否被删去的是其能否在一系列变化之后被一个串完全匹配掉。
- 不管原串如何变化,一堆小串是不动的
由此可以得到一个思路:区间能否被删除取决于小串;而小串是固定的,可以表示出区间变化之后的形态。也就是说,小串可以表示出可以决定删除的DP状态。 那就从小串入手,设计状态f[l][r][i][d]表示区间[l,r]删到最后能否只剩下第i个小串的前d个字符。可以看出,任何一个合法的删除序列,对于区间嵌套起来的一组消除,一定可以用这个状态表示。转移的时候需要一个辅助状态c[l][r],表示区间[l,r]能否被完全消除,然后就做就行了。
Tips
设计DP状态的时候要找决定答案的量,要找不变或者确定可知的量。
Code
1 | #include "lucida" |