7.15 7.15 7.15 前还在搞文化课,错过了前两场,现在回来补qwq。
然而很多题并不会。
比赛传送门
给出一个字符串,定义一个字符串 s 1 s_1 s1 ~ s m s_m sm 对应的 B 1 B_1 B1 ~ B m B_m Bm 满足 B i = min 1 ≤ j < i , s j = s i { i − j } B_i=\min_{1\leq j < i,s_j=s_i}\{i-j\} Bi=min1≤j<i,sj=si{i−j},如果不存在这样的 j j j,那么 B i = 0 B_i=0 Bi=0,现在要求将 s s s 的所有后缀按照其对应的 B B B 序列的字典序排列。
牛客那篇似乎是唯一的题解就是我发的qwq
题解很神,用到一个神仙结论直接切飞,原文为(为了好看些,我自己稍微加了点 LaTeX 元素):
Let C i = m i n i < j ≤ n , s j = s i { j − i } C_i = min_{i
Ci=mini<j≤n,sj=si{j−i}
The B-Suffix Array is equivalent to the suffix array of C 1 C 2 . . . C n C_1~C_2~...~C_n C1 C2 ... Cn
有了这个结论,加一点代码实现细节,就可以 A C AC AC 这题。
这里先讲做法,再讲证明。
对于不存在这样的 j j j 的 C i C_i Ci,我们要让他比其他 C i C_i Ci 都大,设为 C i = n C_i=n Ci=n,然后最后再放一个 n + 1 n+1 n+1 在 C C C 的末尾,最后求出 C C C 的后缀数组,去掉最后一位倒着输出就是答案。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 1000010
int n,s[maxn];char S[maxn];
int fir[maxn],sec[maxn],sa[maxn],c[maxn];
void get_SA()
{
int m=n;
for(int i=1;i<=n;i++)c[fir[i]=s[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[fir[i]]--]=i;
for(int k=1,tot;k<n;k<<=1)
{
tot=0;
for(int i=n-k+1;i<=n;i++)sec[++tot]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)sec[++tot]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[fir[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[fir[sec[i]]]--]=sec[i];
for(int i=1;i<=n;i++)swap(fir[i],sec[i]);
fir[sa[1]]=tot=1;
for(int i=2;i<=n;i++)
if(sec[sa[i]]==sec[sa[i-1]]&&sec[sa[i]+k]==sec[sa[i-1]+k])fir[sa[i]]=tot;
else fir[sa[i]]=++tot;
if(tot==n)break; m=tot;
}
}
int main()
{
while(~scanf("%d",&n))
{
scanf("%s",S+1);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)if(S[j]==S[i]){s[i]=j-i;break;}
if(!s[i])s[i]=n;
}
n++;s[n]=n;get_SA();
for(int i=n-1;i>=1;i--)printf("%d ",sa[i]);printf("\n");
for(int i=1;i<=n;i++)s[i]=fir[i]=sec[i]=sa[i]=c[i]=0;
}
}
证明:
首先对 B B B 和 C C C 要有一点感性的认识, B i B_i Bi 相当于前面最近的一个与 s i s_i si 相同的字符到 i i i 的距离,而 C i C_i Ci 相当于后面的最近的与 s i s_i si 相同的字符到 i i i 的距离。
对于第 i i i 个位置,假设位置 j j j 是后面的最近的相同字符,那么可以发现, B j = C i B_j=C_i Bj=Ci,也就是说,将 B B B 左移一位
就能得到 C C C,这里的左移一位
就是将 B i B_i Bi 放到上一个与 s i s_i si 字符相同的位置。
考虑一个特殊的情况。
对于一个后缀,考虑 B B B 数组开头的一段,肯定是这样的:
0 1 1 1 0 . . . 0~1~1~1~0~... 0 1 1 1 0 ...
那两个 0 0 0 就是第一次出现的 a a a 和 b b b 字符,因为他们没有上一个相同的字符。
考虑两个 0 0 0 中间 1 1 1 序列的长度,他的长度越长,字典序就会越大,例如这两个 B B B 数组:
0 1 1 1 0 . . . 0 1 1 0 . . . 0~1~1~1~0~...\\ 0~1~1~0~...~~~ 0 1 1 1 0 ...0 1 1 0 ...
显然下面那个字典序要大,因为第四位它是 0 0 0 而上面那个是 1 1 1。
然后考虑他们对应的 C C C 数组,是长这样的:
1 1 1 x 1 . . . 1 1 y 1 . . . 1~1~1~x~1~...\\ 1~1~y~1~...~~~ 1 1 1 x 1 ...1 1 y 1 ...
其中, x , y x,y x,y 都大于 1 1 1。
此时可以发现,上面那个的字典序反而要小了。
也就是说,对于开头的一段 1 1 1 长度不同的两个后缀,如果 X X X 的 B B B 比 Y Y Y 的 B B B 字典序要小,那么 X X X 的 C C C 一定比 Y Y Y 的 C C C 的字典序要大。
再考虑一般情况。
假设两个后缀 X , Y X,Y X,Y 的 B B B 序列有一段前缀是相同的,那么 X , Y X,Y X,Y 的这一段前缀肯定也是相同的,不妨设这段前缀以若干个 a a a 结尾,那么对于第一个不同的位,肯定满足一个后缀的这一位是 a a a,另一个后缀的这一位是 b b b,像这样:
. . . a a a a b . . . . . . a a b . . . ...aaaab...\\ ...aab...~~~~ ...aaaab......aab...
第一个不同的位是这一段中的第三位,然后他们对应的 B B B 序列就是:
. . . a a a a b . . . . . . 1111 x . . . . . . a a b . . . . . . 11 y . . . ...aaaab...\\ ...1111x...\\ ...aab...~~~~\\ ...11y...~~~~ ...aaaab......1111x......aab... ...11y...
第三位的 1 1 1 比 y y y 小,所以后缀 X X X 的 B B B 的字典序比后缀 Y Y Y 的 B B B 的字典序要小。
再考虑他们的 C C C 序列,也就是将 B B B 左移一位
,由于他们 B B B 序列的前面部分都是相同的,所以上一个 b b b 出现的位置是相同的,左移一位
后 x x x 和 y y y 就会同时移到那个位置,像这样:
b b b a a . . . 11 x 11... b b b a a . . . 11 y 11... bbbaa...\\ 11x11...\\ bbbaa...\\ 11y11... bbbaa...11x11...bbbaa...11y11...
然后你可以发现,在 x , y x,y x,y 之前的位都是相同的,也就是说, x , y x,y x,y 之间的大小比较决定了这两个后缀的字典序。
观察一开始 x , y x,y x,y 的位置,可以发现 x x x 那个位置离上一个 b b b 要更远一些,即满足 x > y x>y x>y,所以此时 X X X 的 C C C 的字典序比 Y Y Y 的 C C C 的字典序要大。
综合上述两种情况,可以知道,对于两个后缀 X , Y X,Y X,Y,假如 X X X 的 B B B 的字典序比 Y Y Y 的 B B B 的字典序要小,那么总有 X X X 的 C C C 的字典序比 Y Y Y 的 C C C 的字典序要大。
所以,求出 C C C 的后缀数组再反过来就是答案。
证毕。
仔细思考,可以发现这个 C C C 相对于 B B B 的优越性就在于:一个后缀的 C C C 一定是 整个序列的 C C C 的 一段后缀,并且这两个后缀的位置是相对应的,所以将 C C C 求出后缀数组,就相当于将原字符串的后缀进行排序了。而 B B B 不一样,对于每个后缀,他的 B B B 不一定是 整个序列的 B B B 的 一段后缀,所以不能直接上后缀排序。
再讲一个细节,就是为什么末尾要放一个 n + 1 n+1 n+1。
假如末尾是 a b ab ab 的话,就对应两个后缀: a b ab ab 和 b b b,他们的 B B B 数组分别为 00 00 00 和 0 0 0,所以 b b b 的 B B B 的字典序比 a b ab ab 的小,那么 b b b 应该排在 a b ab ab 前面。
而注意到我们最后是要将 C C C 的 s a sa sa 数组反过来的,也就是说,反过来之前, b b b 的 C C C 应该比 a b ab ab 的要大。
但是事实上他们的 C C C 分别为 n n n 和 n n nn nn, b b b 的 C C C 是要比 a b ab ab 的 C C C 小的,而在末尾加一个 n + 1 n+1 n+1 就可以避免这个问题,具体可以结合样例的第三组数据理解。
令 m i n d i v ( i ) mindiv(i) mindiv(i) 为 i i i 的最小值因子,让点 i i i 与点 i m i n d i v ( i ) \frac i {mindiv(i)} mindiv(i)i 连边,设 δ ( i , j ) \delta(i,j) δ(i,j) 为树中 i , j i,j i,j 两点间的距离,现在要你找到一个 u u u,使 ∑ i = 1 n w i δ ( u , i ! ) \sum_{i=1}^n w_i \delta(u,i!) ∑i=1nwiδ(u,i!) 最小。
显然,我们不需要造出整棵树,只需要造出与 1 ! , 2 ! , 3 ! , . . . , n ! 1!,2!,3!,...,n! 1!,2!,3!,...,n! 相关的部分即可。
先不管怎么造这棵树,假设我们现在有这棵树。
先令 u = 1 u=1 u=1,然后逐步往下走。设 s u m i sum_i sumi 为子树内所有点的 w w w 之和,假如往儿子 v v v 走更优,即令 u = v u=v u=v 更优,需要满足 s u m 1 − s u m v < s u m v sum_1-sum_v
显然,对于点 u u u,他只有至多一个儿子能满足这个不等式,因为这个不等式等价于 s u m v > s u m 1 2 sum_v>\dfrac {sum_1} 2 sumv>2sum1,显然不可能有两个儿子同时满足,所以从点 1 1 1 出发,往下走的路径是唯一的。
然后就是要考虑怎么造这棵树,我们注意到,树上有很多点,他们不等于任意一个 i ! i! i!,只是在某个 i ! i! i! 到根节点的路径上,假如某个这种点只有一个儿子,那么这个点其实是对决策没有贡献的,所以,可以考虑建出它的虚树,然后在虚树上跑上面的过程。
考虑 i ! i! i! 和 ( i + 1 ) ! (i+1)! (i+1)! 的 l c a lca lca 是谁。对于一条 i i i 与 i m i n d i v ( i ) \frac i {mindiv(i)} mindiv(i)i 的边,假如将 m i n d i v ( i ) mindiv(i) mindiv(i) 作为权值放到边上,观察根节点 1 ! 1! 1! 到点 i ! i! i! 的路径,经过的边权是严格不上升的,也就是会先经过 i ! i! i! 的较大的质因子。而 ( i + 1 ) ! (i+1)! (i+1)! 比 i ! i! i! 多乘了个 i i i,也就是说, i + 1 i+1 i+1 的最大的质因子会使得 1 ! 1! 1! 到 i ! i! i! 和 ( i + 1 ) ! (i+1)! (i+1)! 的路径分叉,而这个分叉点就是要求的 l c a lca lca。
于是可以用树状数组维护之前 i ! i! i! 的各个质因子的出现次数,然后统计不小于 i + 1 i+1 i+1 的最大质因子的质因子个数,就能得到 l c a lca lca 的深度,然后就可以建虚树了。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 200010
#define ll long long
int n,w[maxn];ll ans=0;
int mindiv[maxn];void work(){
for(int i=2;i<=maxn-10;i++)
for(int j=i;j<=maxn-10;j+=i)
if(!mindiv[j])mindiv[j]=i;
}
int d[maxn],lcad[maxn],zhan[maxn],t=0;
struct edge{int y,next;}e[maxn<<1];
int first[maxn],len=0,tr[maxn];
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
void add(int x){for(;x<=n;x+=(x&-x))tr[x]++;}
int sum(int x){int re=0;for(;x>=1;x-=(x&-x))re+=tr[x];return re;}
void buildtree()
{
for(int i=2;i<=n;i++)
{
d[i]=d[i-1]+1;int j=i;
for(;j!=mindiv[j];j/=mindiv[j])d[i]++;
lcad[i]=sum(n)-sum(j-1);
for(j=i;j>1;j/=mindiv[j])add(mindiv[j]);
}
zhan[t=1]=1;for(int i=2,tot=n;i<=n;i++){
while(t>1&&d[zhan[t-1]]>=lcad[i])buildroad(zhan[t-1],zhan[t]),t--;
if(d[zhan[t]]!=lcad[i])d[++tot]=lcad[i],buildroad(tot,zhan[t]),zhan[t]=tot;
zhan[++t]=i;
}
while(t>1)buildroad(zhan[t-1],zhan[t]),t--;
}
void dfs1(int x){
ans+=1ll*w[x]*d[x];
for(int i=first[x];i;i=e[i].next)
dfs1(e[i].y),w[x]+=w[e[i].y];
}
void dfs2(int x){
for(int i=first[x];i;i=e[i].next)
if(2*w[e[i].y]>w[1])ans+=1ll*(w[1]-2*w[e[i].y])*(d[e[i].y]-d[x]),dfs2(e[i].y);
}
void init(){len=ans=0;for(int i=1;i<=2*n;i++)first[i]=d[i]=lcad[i]=w[i]=tr[i]=0;}
int main()
{
work();while(~scanf("%d",&n)){
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
buildtree();dfs1(1);dfs2(1);
printf("%lld\n",ans);init();
}
}
给出 n n n 个三维向量 s s s,现在重新定义向量的乘法: x ⃗ ( a 1 , a 2 , a 3 ) × y ⃗ ( b 1 , b 2 , b 3 ) = ( ( a 1 b 1 + a 2 b 3 + a 3 b 2 ) , ( a 2 b 1 + a 3 b 3 + a 1 b 2 ) , ( a 3 b 1 + a 2 b 3 + a 1 b 2 ) ) \vec x(a_1,a_2,a_3)\times \vec y(b_1,b_2,b_3)=(~(a_1b_1+a_2b_3+a_3b_2)~,~(a_2b_1+a_3b_3+a_1b_2)~,~(a_3b_1+a_2b_3+a_1b_2)~) x(a1,a2,a3)×y(b1,b2,b3)=( (a1b1+a2b3+a3b2) , (a2b1+a3b3+a1b2) , (a3b1+a2b3+a1b2) ),现在有 q q q 次询问,第 i i i 次询问询问给出 e i , 1 e_{i,1} ei,1 ~ e i , n e_{i,n} ei,n,求出 s 1 e i , 1 × s 2 e i , 2 × . . . × s n e i , n s_1^{e_{i,1}}\times s_2^{e_{i,2}}\times ...\times s_n^{e_{i,n}} s1ei,1×s2ei,2×...×snei,n。
仔细观察,发现这个乘法
是满足结合律的,证明如下(其实就是暴力算):
设 x ⃗ ( a 1 , a 2 , a 3 ) , y ⃗ ( b 1 , b 2 , b 3 ) , z ⃗ ( c 1 , c 2 , c 3 ) \vec x(a_1,a_2,a_3),\vec y(b_1,b_2,b_3),\vec z(c_1,c_2,c_3) x(a1,a2,a3),y(b1,b2,b3),z(c1,c2,c3),假如按顺序计算,就有:
x ⃗ × y ⃗ = ( ( a 1 b 1 + a 2 b 3 + a 3 b 2 ) , ( a 2 b 1 + a 3 b 3 + a 1 b 2 ) , ( a 3 b 1 + a 1 b 3 + a 2 b 2 ) ) \vec x\times \vec y=(~(a_1b_1+a_2b_3+a_3b_2)~,~(a_2b_1+a_3b_3+a_1b_2)~,~(a_3b_1+a_1b_3+a_2b_2)~) x×y=( (a1b1+a2b3+a3b2) , (a2b1+a3b3+a1b2) , (a3b1+a1b3+a2b2) )
( x ⃗ × y ⃗ ) × z ⃗ = ( (\vec x\times \vec y)\times \vec z=( (x×y)×z=(
( a 1 b 1 c 1 + a 2 b 3 c 1 + a 3 b 2 c 1 + a 2 b 1 c 3 + a 3 b 3 c 3 + a 1 b 2 c 3 + a 3 b 1 c 2 + a 1 b 3 c 2 + a 2 b 2 c 2 ) , (a_1b_1c_1+a_2b_3c_1+a_3b_2c_1+a_2b_1c_3+a_3b_3c_3+a_1b_2c_3+a_3b_1c_2+a_1b_3c_2+a_2b_2c_2), (a1b1c1+a2b3c1+a3b2c1+a2b1c3+a3b3c3+a1b2c3+a3b1c2+a1b3c2+a2b2c2),
( a 2 b 1 c 1 + a 3 b 3 c 1 + a 1 b 2 c 1 + a 3 b 1 c 3 + a 1 b 3 c 3 + a 2 b 2 c 3 + a 1 b 1 c 2 + a 2 b 3 c 2 + a 3 b 2 c 2 ) , (a_2b_1c_1+a_3b_3c_1+a_1b_2c_1+a_3b_1c_3+a_1b_3c_3+a_2b_2c_3+a_1b_1c_2+a_2b_3c_2+a_3b_2c_2), (a2b1c1+a3b3c1+a1b2c1+a3b1c3+a1b3c3+a2b2c3+a1b1c2+a2b3c2+a3b2c2),
( a 3 b 1 c 1 + a 1 b 3 c 1 + a 2 b 2 c 1 + a 1 b 1 c 3 + a 2 b 3 c 3 + a 3 b 2 c 3 + a 2 b 1 c 2 + a 3 b 3 c 2 + a 1 b 2 c 2 ) ) (a_3b_1c_1+a_1b_3c_1+a_2b_2c_1+a_1b_1c_3+a_2b_3c_3+a_3b_2c_3+a_2b_1c_2+a_3b_3c_2+a_1b_2c_2)) (a3b1c1+a1b3c1+a2b2c1+a1b1c3+a2b3c3+a3b2c3+a2b1c2+a3b3c2+a1b2c2))
先计算后两者,则有:
y ⃗ × z ⃗ = ( ( b 1 c 1 + b 2 c 3 + b 3 c 2 ) , ( b 2 c 1 + b 3 c 3 + b 1 c 2 ) , ( b 3 c 1 + b 1 c 3 + b 2 c 2 ) ) \vec y\times \vec z=(~(b_1c_1+b_2c_3+b_3c_2)~,~(b_2c_1+b_3c_3+b_1c_2)~,~(b_3c_1+b_1c_3+b_2c_2)~) y×z=( (b1c1+b2c3+b3c2) , (b2c1+b3c3+b1c2) , (b3c1+b1c3+b2c2) )
x ⃗ × ( y ⃗ × z ⃗ ) = ( \vec x\times (\vec y \times \vec z)=( x×(y×z)=(
( a 1 b 1 c 1 + a 1 b 2 c 3 + a 1 b 3 c 2 + a 2 b 3 c 1 + a 2 b 1 c 3 + a 2 b 2 c 2 + a 3 b 2 c 1 + a 3 b 3 c 3 + a 3 b 1 c 2 ) , (a_1b_1c_1+a_1b_2c_3+a_1b_3c_2+a_2b_3c_1+a_2b_1c_3+a_2b_2c_2+a_3b_2c_1+a_3b_3c_3+a_3b_1c_2), (a1b1c1+a1b2c3+a1b3c2+a2b3c1+a2b1c3+a2b2c2+a3b2c1+a3b3c3+a3b1c2),
( a 2 b 1 c 1 + a 2 b 2 c 3 + a 2 b 3 c 2 + a 3 b 3 c 1 + a 3 b 1 c 3 + a 3 b 2 c 2 + a 1 b 2 c 1 + a 1 b 3 c 3 + a 1 b 1 c 2 ) , (a_2b_1c_1+a_2b_2c_3+a_2b_3c_2+a_3b_3c_1+a_3b_1c_3+a_3b_2c_2+a_1b_2c_1+a_1b_3c_3+a_1b_1c_2), (a2b1c1+a2b2c3+a2b3c2+a3b3c1+a3b1c3+a3b2c2+a1b2c1+a1b3c3+a1b1c2),
( a 3 b 1 c 1 + a 3 b 2 c 3 + a 3 b 3 c 2 + a 1 b 3 c 1 + a 1 b 1 c 3 + a 1 b 2 c 2 + a 2 b 2 c 1 + a 2 b 3 c 3 + a 2 b 1 c 2 ) ) (a_3b_1c_1+a_3b_2c_3+a_3b_3c_2+a_1b_3c_1+a_1b_1c_3+a_1b_2c_2+a_2b_2c_1+a_2b_3c_3+a_2b_1c_2)) (a3b1c1+a3b2c3+a3b3c2+a1b3c1+a1b1c3+a1b2c2+a2b2c1+a2b3c3+a2b1c2))
计算完后,经过 9 2 × 3 9^2\times 3 92×3 次的人工对比,上下两个向量是相等的。你可以试试,很有趣
得证。
于是每个询问用快速幂搞一搞就好了,而且这个题可以预处理很多东西来加速,但是由于我比较懒所以我并没有写qwq。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 100010
#define ll long long
#define mod 998244353
int n,m,q;ll z,a,b;
struct vec{
int s[3];void init(){s[0]=1;s[1]=s[2]=0;}
vec operator *(const vec &B){return (vec){
(int)((1ll*s[0]*B.s[0]%mod+1ll*s[1]*B.s[2]%mod+1ll*s[2]*B.s[1]%mod)%mod),
(int)((1ll*s[1]*B.s[0]%mod+1ll*s[2]*B.s[2]%mod+1ll*s[0]*B.s[1]%mod)%mod),
(int)((1ll*s[2]*B.s[0]%mod+1ll*s[0]*B.s[2]%mod+1ll*s[1]*B.s[1]%mod)%mod)};
}
}v[maxn],ans;
__int128 e[21][maxn],bin[maxn];
const __int128 mod2=(__int128)mod*mod-1;
const ll mod3=1ll<<32;
vec ksm(vec x,__int128 y)
{
vec re;re.init();
while(y)
{
if(y&1)re=re*x;
x=x*x;y>>=1;
}
return re;
}
int main()
{
scanf("%d %d %d %lld %lld %lld",&n,&m,&q,&z,&a,&b);
for(int i=1;i<=n;i++)for(int j=0;j<=2;j++)z=(z*a+b)%mod3,v[i].s[j]=z%mod;
bin[0]=1;for(int i=1;i<=1000;i++)bin[i]=bin[i-1]*2%mod2;
for(int k=1;k<=q;k++)for(int i=1;i<=n;i++){
e[k][i]=0;for(int j=0;j<m;j++)
z=(z*a+b)%mod3,e[k][i]=(__int128)(e[k][i]+bin[32*j]*z)%mod2;
}
for(int i=1;i<=q;i++){
ans.init();
for(int j=1;j<=n;j++)ans=ans*ksm(v[j],e[i][j]);
printf("%d %d %d\n",ans.s[0],ans.s[1],ans.s[2]);
}
}
给出两个字符串 A , B A,B A,B,定义 x ∞ x^{\infty} x∞ 表示将 x x x 复制无限次得到的字符串,现在要你比较 A ∞ A^{\infty} A∞ 和 B ∞ B^{\infty} B∞ 的字典序大小。
假设他们的长度分别为 n , m n,m n,m,不妨令 n > m n>m n>m,那么其实我们只需要比较前 n + m − gcd ( n , m ) n+m-\gcd(n,m) n+m−gcd(n,m) 位即可,假如前 n + m − gcd ( n , m ) n+m-\gcd(n,m) n+m−gcd(n,m) 位都相同,那么有 A ∞ = B ∞ A^{\infty}=B^{\infty} A∞=B∞
可以发现,如果 A ∞ = B ∞ A^{\infty}=B^{\infty} A∞=B∞,那么循环节长度肯定是 gcd ( n , m ) \gcd(n,m) gcd(n,m),这里用到一个引理——Periodicity Lemma:如果 p , q p,q p,q 是 S S S 的循环节,并且 p + q ≤ ∣ S ∣ + gcd ( p , q ) p+q\leq |S|+\gcd(p,q) p+q≤∣S∣+gcd(p,q),那么 gcd ( p , q ) \gcd(p,q) gcd(p,q) 也是 S S S 的循环节。
证明:
分类讨论一下:假如 m = gcd ( n , m ) m=\gcd(n,m) m=gcd(n,m),那么显然满足。
假如 m ≠ gcd ( n , m ) m\neq \gcd(n,m) m=gcd(n,m),并且 A ∞ , B ∞ A^{\infty},B^{\infty} A∞,B∞ 的前 n + m − gcd ( n , m ) n+m-\gcd(n,m) n+m−gcd(n,m) 位相同的话,可以发现这样的规律:
绿色部分和蓝色部分是相同的,可以发现,蓝色部分长度为 n m o d m n\bmod m nmodm,则绿色部分长度为 m − n m o d m m-n\bmod m m−nmodm,分别设为 a , b a,b a,b,那么 B B B 的前后 a a a 位是相同的,前后 b b b 位也是相同的。
类似的,又可以得到 B B B 的前后 m m o d a m\bmod a mmoda 位是相同的。
那么重复这个过程,相当于在做辗转相除法,最后就得到了 B B B 的前后 gcd ( n , m ) \gcd(n,m) gcd(n,m) 位是相同的,稍微再搞一搞就变成了每 gcd ( n , m ) \gcd(n,m) gcd(n,m) 位都相同。
给出一张网络,边带费用,每次询问给出 u , v u,v u,v,表示每条边的流量为 u v \dfrac u v vu,问从 1 1 1 往 n n n 流 1 1 1 个单位的流量需要的最小费用。
不妨将所有流量除乘以 v u \dfrac v u uv,那么网络中每条边的流量就是 1 1 1,需要的流量就是 v u \dfrac v u uv。
由于网络中每条边的流量固定了,即不会因询问而改变,所以我们可以先跑一次费用流,求出所有路径的费用,然后每次从小的开始取知道流量满足 v u \dfrac v u uv 即可。
一个教训:分数类的运算很慢,可以避免的话还是不要写的好。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 10010
#define ll long long
int n,m,k;
struct edge{int x,y,z,cost,next;}e[maxn<<1];
int first[maxn],len;
void buildroad(int x,int y,int z,int cost){e[++len]=(edge){x,y,z,cost,first[x]};first[x]=len;}
int q[maxn],st,ed,fa[maxn],dis[maxn],t;ll road[maxn];
bool v[maxn];
bool SPFA()
{
memset(fa,0,(n+1)<<2);
memset(dis,63,(n+1)<<2);
st=1,ed=2;q[st]=1;dis[1]=0;
while(st!=ed)
{
int x=q[st++];st=st>n?1:st;v[x]=false;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(e[i].z&&dis[y]>dis[x]+e[i].cost){
dis[y]=dis[x]+e[i].cost;fa[y]=i;
if(!v[y])v[q[ed++]=y]=true,ed=ed>n?1:ed;
}
}
}
return fa[n];
}
ll ans1,ans2;
ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
int main()
{
while(~scanf("%d %d",&n,&m))
{
memset(first,0,(n+1)<<2);len=1;t=0;
for(int i=1,x,y,z;i<=m;i++)scanf("%d %d %d",&x,&y,&z),
buildroad(x,y,1,z),buildroad(y,x,0,-z);
while(SPFA()){
road[++t]=0;
for(int i=fa[n];i;i=fa[e[i].x])
road[t]+=e[i].cost,e[i].z--,e[i^1].z++;
}
scanf("%d",&k);
for(int i=1,x,y;i<=k;i++)
{
scanf("%d %d",&x,&y);ans1=0,ans2=1;
if((ll)t*x<(ll)y){printf("NaN\n");continue;};
int now=1,xx=x,yy=y,vk=0;//vk是一个优化,可以防止ans1爆long long
while(y>=x)ans1=ans1+road[now++],y-=x;
if(y)ans1=ans1*x+y*road[now],vk=1;
int p=gcd(ans1,ans2);ans1/=p,ans2/=p;
if(!vk)ans1*=xx;ans2*=yy;
p=gcd(ans1,ans2);ans1/=p,ans2/=p;
printf("%lld/%lld\n",ans1,ans2);
}
}
}
给出一张图,询问是否存在一个满足要求的子图,其中点 i i i 的度为 d [ i ] d[i] d[i]。
因为 d [ i ] d[i] d[i] 只能为 1 1 1 或 2 2 2,假如全部都是 1 1 1,那么就是个带花树板子了。
假如是 2 2 2 的话,也就是说这个点可以匹配两次,那么可以将这个点 x x x 拆成两个点 x 1 , x 2 x_1,x_2 x1,x2。
而对于一条边,假如他连接的两个点 x , y x,y x,y 的 d d d 都是 2 2 2,那么不能单纯将 x 1 , x 2 x_1,x_2 x1,x2 与 y 1 , y 2 y_1,y_2 y1,y2 连边,这样连边可能出现 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) 这样的配对,相当于将原边用了两次。
为了让原边只用一次,考虑像网络流一样拆开这条边,新建两个点,分别为左点和右点,在这两个点之间连边,然后 x 1 , x 2 x_1,x_2 x1,x2 向左点连边, y 1 , y 2 y_1,y_2 y1,y2 向右点连边。
然后跑一下带花树,假如能够完美匹配,就说明存在满足要求的子图。
为什么能这样拆边?
假如跑带花树时, x 1 , x 2 x_1,x_2 x1,x2 其中一个和左点匹配了,那么为了完美匹配,右点肯定和 y 1 , y 2 y_1,y_2 y1,y2 其中一个匹配了,那么这种情况就相当于将原边放入了最终的子图。
假如左点和右点匹配了,那么原边就没有对任何点做出贡献,即不在子图中。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 100010
int n,m,d[maxn],ans;
struct edge{int y,next;}e[maxn<<1];
int first[maxn],len;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
void ins(int x,int y){buildroad(x,y);buildroad(y,x);}
int mat[maxn],col[maxn],pre[maxn],fa[maxn];
int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}
int q[maxn<<1],st,ed,dfn[maxn],id=0;
int lca(int x,int y){
for(id++;x=findfa(x),dfn[x]!=id;swap(x,y))
if(x)dfn[x]=id,x=pre[mat[x]]; return x;
}
void blossom(int x,int y,int z)
{
while(findfa(x)!=z)
{
pre[x]=y;y=mat[x];
if(col[y]==2)col[y]=1,q[++ed]=y;
fa[x]=fa[y]=z;x=pre[y];
}
}
bool bfs(int S)//带花树板子
{
for(int i=1;i<=2*n+2*m;i++)fa[i]=i;
memset(pre,0,(2*n+2*m+1)<<2);
memset(col,0,(2*n+2*m+1)<<2);
col[q[st=ed=1]=S]=1;
for(int x=q[st];st<=ed;x=q[++st])
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;if(col[y]==2||findfa(x)==findfa(y))continue;
if(col[y]==1){int LCA=lca(x,y);blossom(x,y,LCA);blossom(y,x,LCA);}
else if(!col[y]){
col[y]=2;pre[y]=x;
if(!mat[y]){
for(int j,to;y;y=to)j=pre[y],to=mat[j],mat[y]=j,mat[j]=y;
return true;
} col[mat[y]]=1;q[++ed]=mat[y];
}
}
return false;
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
memset(first,0,(2*n+2*m+1)<<2);len=0;
memset(mat,0,(2*n+2*m+1)<<2);
ans=0;for(int i=1;i<=n;i++)scanf("%d",&d[i]),ans+=d[i];//ans统计总点数
for(int i=1,x,y;i<=m;i++)
{
scanf("%d %d",&x,&y);
if(d[x]==d[y]&&d[x]==1)ins(x,y);
else if(d[y]==1)ins(x,y),ins(x+n,y);
else if(d[x]==1)ins(x,y),ins(x,y+n);
else ins(x,i+2*n),ins(x+n,i+2*n),ins(y,i+2*n+m),ins(y+n,i+2*n+m),ins(i+2*n,i+2*n+m),ans+=2;
}
for(int i=1;i<=2*n+2*m;i++)ans-=(!mat[i]&&bfs(i))*2;
if(!ans)printf("Yes\n");else printf("No\n");
}
}