提咋选谈
[LNOI2022] 盒
先考虑固定\(B\)
直接考虑\(i\rightarrow i+1\)会经过多少次,对于\([1,i]\)这个整体,我们到外界的连边只有\(i\rightarrow i+1\),所以前缀的差值一定会经过他
因此其答案为\(\sum\limits_{i=1}^n|Suma_i-Sumb_i|w_i\)
根据这个我们可以搞一个\(O(nS)\)的\(dp\),\(dp_{i,S}\)表示前\(i\)个当前前缀和为\(S\)的答案
\(dp_{i,S}=|Suma_i-S|Sw_i+Sum_{i-1,S}\)
我们考虑枚举每个\(Sumb_i\)
即答案为\(\sum\limits_{i=1}^nw_i\sum\limits_{j=0}^S|Suma_i-j|Cnt(i,j)\)
其中\(Cnt(i,j)\)表示\(Sumb_i=j\)的方案
考虑有\(n\)个盒子,可以在里面放任意球
则原问题等价与在\([1,i]\)里放\(j\)球,在\([i+1,n]\)里方\(S-j\)球
则\(Cnt(i,j)=\binom{i+j-1}{j}\binom{n+S-i-j}{S-j}\)
即\(\sum\limits_{i=1}^nw_i\sum\limits_{j=0}^S|Suma_i-j|\binom{i+j-1}{j}\binom{n+S-i-j-1}{S-j}\)
拆绝对值
\(\sum\limits_{i=1}^nw_i\sum\limits_{j=0}^{Suma_i}(Suma_i-j)\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}+\sum\limits_{j=Suma_i+1}^{S}(j-Suma_i)\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}\)
观察一下式子,不妨再把括号拆开
即\(\sum\limits_{i=1}^nw_i[Suma_i(\sum\limits_{j=0}^{Suma_i}\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}-\sum\limits_{j=Suma_i+1}^{S}\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1})\)
\(+\sum\limits_{j=Suma_i+1}^{S}j\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}-\sum\limits_{j=0}^{Suma_i}j\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}]\)
这里我们设\(f(n,S,i,k)=\sum\limits_{j=0}^{k}\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}\),很明显上式可以容斥
注意到\(j\binom{i+j-1}{j}=i\binom{i+j-1}{i}\)
则\(\sum\limits_{j=0}^{k}j\binom{i+j-1}{j}\binom{n+S-i-j-1}{n-i-1}=i\sum\limits_{j=0}^{k}\binom{i+j-1}{j-1}\binom{n+S-i-j-1}{n-i-1}=i\sum\limits_{j=0}^{k-1}\binom{i+j}{j}\binom{n+S-i-j-2}{n-i-1}\)
\(=i\sum\limits_{j=0}^{k-1}\binom{(i+1)+j-1}{j}\binom{n+S-(i+1)-j-1}{n-(i+1)}=i\sum\limits_{j=0}^{k-1}\binom{(i+1)+j-1}{j}\binom{(n+1)+(S-1)-(i+1)-j-1}{(S-1)-j}=if(n+1,S-1,i+1,k-1)\)
因此我们只需计算\(f(n,S,i,S),f(n,S,i,Suma_i),f(n+1,S-1,i+1,S-1),f(n+1,S-1,i+1,Suma_i-1)\)即可
移动\(i\)的规律不好找,我们要对\(f\)做一些变形
我们认为\(f\)的求和相当于枚举前\(i\)个盒子球的个数,其中它的数量不超过\(k\)
换个思路,这里我们可以枚举第\(k+1\)的球放的位置\(j\),这时强制\(j\)放,\([1,j],[j,n]\)分别放\(k,S-k-1\)个球
即\(f(n,S,i,k)=\sum\limits_{j=i+1}^n\binom{j-1+k}{j-1}\binom{n-j+S-k-1}{n-j}\)
回到\(f\)的定义\(f(n,S,i,k)=\sum\limits_{j=0}^{k}\binom{i+j-1}{i-1}\binom{n+S-i-j-1}{n-i-1}\)
在实际过程中,\(Suma_i\)是递增的所以我们可以移利用上面的式子\(i,k\),移的次数也是线性的
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
const int MAXN=4e6+5;
int Pow(int a,int b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int inv(int a,int p)
{
return Pow(a,p-2,p);
}
int inv_fac[MAXN];
int fac[MAXN];
int C(int n,int m)
{
if(n<m||m<0)
{
return 0;
}
if(n==m||m==0)
{
return 1;
}
return ((((long long)fac[n]*inv_fac[m])%MOD)*inv_fac[n-m])%MOD;
}
struct Node{
int n,S,i,k;
int Ans;
void To(int I,int K)
{
while(i<I)
{
i++;
int Lp=((long long)C(i-1+k,i-1)*C(n-i+S-k-1,n-i))%MOD;
Ans=((long long)Ans-Lp+MOD)%MOD;
}
while(k<K)
{
k++;
int Lp=((long long)C(i+k-1,i-1)*C(n+S-i-k-1,n-i-1))%MOD;
Ans=((long long)Ans+Lp+MOD)%MOD;
}
return;
}
}A1,A2,A3,A4;
int T;
int n;
int a[MAXN];
int Suma[MAXN];
int w[MAXN];
int main()
{
fac[0]=1;
for(int i=1;i<=MAXN-5;i++)
{
fac[i]=((long long)fac[i-1]*i)%MOD;
}
inv_fac[MAXN-5]=inv(fac[MAXN-5],MOD);
for(int i=MAXN-5-1;i>=1;i--)
{
inv_fac[i]=((long long)inv_fac[i+1]*(i+1))%MOD;
}
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
Suma[i]=Suma[i-1]+a[i];
}
for(int i=1;i<n;i++)
{
scanf("%d",&w[i]);
}
A1.n=n;
A1.S=Suma[n];
A1.i=0;
A1.k=-1;
A1.Ans=0;
A1.To(1,Suma[n]);
A2.n=n;
A2.S=Suma[n];
A2.i=0;
A2.k=-1;
A2.Ans=0;
A2.To(1,0);
A3.n=n+1;
A3.S=Suma[n]-1;
A3.i=0;
A3.k=-1;
A3.Ans=0;
A3.To(1,Suma[n]-1);
A4.n=n+1;
A4.S=Suma[n]-1;
A4.i=0;
A4.k=-1;
A4.Ans=0;
A4.To(1,-1);
int Ans=0;
for(int i=1;i<n;i++)
{
// printf("%d %d %d %d??\n",A1.Ans,A2.Ans,A3.Ans,A4.Ans);
A1.To(i,Suma[n]);
A2.To(i,Suma[i]);
A3.To(i+1,Suma[n]-1);
A4.To(i+1,Suma[i]-1);
int Po=0;
Po=((long long)2*A2.Ans)%MOD;
Po=((long long)Po-A1.Ans+MOD)%MOD;
Po=((long long)Po*Suma[i])%MOD;
int Qo=0;
Qo=((long long)2*A4.Ans)%MOD;
Qo=(MOD-Qo);
Qo=((long long)Qo+A3.Ans)%MOD;
Qo=((long long)Qo*i)%MOD;
Po=((long long)Po+Qo)%MOD;
Po=((long long)Po*w[i])%MOD;
Ans=((long long)Ans+Po)%MOD;
}
printf("%d\n",Ans);
}
}
CF1510J Japanese Game
首先这是个构造
首先考虑一下知道\(P\)求那些一定被染色
我们肯定是把其中的一个连通块左边的全部移到左边,右边额全部移到右边,之后看这个连通块左右移后一定经过的点
我最开始是考虑最左边的,假设是\([L_1,R_1]\)可以还原出他的\(Len\)为\(R_1\),然后他能自由移动的范围是\([1,R_1+Len]\),然后根据这个往后推就行了
不过你每个连同块的长度太长了,能只有操作的空间过小会更容易引冲突
所以我们会尽量固定每个连通块能自由活动的区间
观察样例,\(BRBRBR\)这样交错的能缩小活动的范围且不会贡献一定能染色
手玩一下会发现你每个连通块的活动范围不会大于自身程度加\(3\)
并且这个自由活动的范围是公用的(一直没注意到)
所以我们直接枚举额外的活动范围,把它放在末尾,然后再用\(BR\)填空即可
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e6+5;
int n;
string s;
struct Site{
int l,r;
int Pl;
}a[MAXN];
string S;
int Cnt=0;
int Cp=0;
int Last=0;
int check(int mid)
{
Last=0;
for(int i=1;i<=Cnt;i++)
{
int Len=(a[i].l-a[i-1].r-2-mid);
if(i==Cnt)
{
Len+=2;
}
if(Len<0)
{
return 0;
}
if(Len>0)
{
if(mid==0||Len==1)
{
return 0;
}
if(Len&1)
{
if(mid<=1)
{
return 0;
}
int op=1;
for(int j=Last+1;j-Last<=(Len);j++)
{
if(op)
{
S[j]='B';
}
else
{
S[j]='R';
}
op^=1;
}
Last+=Len;
S[Last-1]='B';
S[Last]='R';
}
else
{
int op=1;
for(int j=Last+1;j-Last<=(Len);j++)
{
if(op)
{
S[j]='B';
}
else
{
S[j]='R';
}
op^=1;
}
Last+=Len;
}
}
if(i==Cnt)
{
continue;
}
for(int j=Last+1;j-Last<=(a[i].r-a[i].l+1+mid);j++)
{
S[j]='B';
}
Last=Last+(a[i].r-a[i].l+1+mid);
Last++;
S[Last]='R';
}
return 1;
}
int main()
{
//freopen("barca.in","r",stdin);
//freopen("barca.out","w",stdout);
cin>>s;
n=s.size();
int R=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='#')
{
if(R==0)
{
++Cnt;
a[Cnt].l=i+1;
R=1;
}
a[Cnt].r=i+1;
}
else
{
R=0;
}
}
int Len=3;
a[0].l=-1;
a[0].r=-1;
for(int i=2;i<=Cnt;i++)
{
Len=min(Len,a[i].l-a[i-1].r);
}
Len=min(Len,n-a[Cnt].r);
a[++Cnt].l=n;
a[Cnt].r=n;
for(int i=0;i<=Len;i++)
{
S.resize(n+1,'R');
if(check(i))
{
Cnt=0;
R=0;
for(int j=1;j<=n;j++)
{
if(S[j]=='B')
{
if(R==0)
{
++Cnt;
a[Cnt].l=j;
R=1;
}
a[Cnt].r=j;
}
else
{
R=0;
}
}
printf("%d\n",Cnt);
for(int j=1;j<=Cnt;j++)
{
printf("%d ",a[j].r-a[j].l+1);
}
return 0;
}
}
printf("-1\n");
}
CF794G Replace All
一个有意思的题
首先不难想到我们会将首尾的\(A,B\)先提前消掉,这样会剩首尾不同
但这就又出现了特殊情况,\(A=B\)的情况,我们可以算,很明显\(S,T\)任取,答案为\((\sum\limits_{i=1}^n(2^i))^2\)
剔除这种情况,假设\(|S|>|T|\)我们不难发现\(T\)是\(S\)的前缀和后缀
进一步的,\(S-T\)也会匹配到\(S\),或\(T\),不过\(|T|\),\(|S-T|\)一定是前缀的关系,具体要看谁大
模拟这个过程,发现其实他是类似于更相减损术,一直到减到呈倍数关系,这时小的那个很明显是循环节
我们设循环节为\(C\),则\(|C|=\gcd(|A|,|B|)\)
整理一下,如果\(A\not=B\),则\(S,T\)的选择和字符的位置无关
假设已经钦定\(?\)的选择
我们设\(a_d,a_f,b_d,b_f\)分别表示其数量
显然有\((a_d-a_f)|S|=(b_f-b_d)|T|\),不妨直接有\(a,b\)替换
\(|S|,|T|\)直接有\(|C|k_s,|C|k_s\)来替换,于是\(ak_s=bk_t\),\(\gcd(k_s,k_t)=1\)
注意一下,\(a,b\)要共号,或者\(a=0,b=0\)
再分讨一下
\(a,b=0\),很明显,\(k_s,k_b\)没限制,我们直接枚举\(a,b\)长度,然后有确定\(|C|\)的长度
即\(\sum\limits_{i=1}^n\sum\limits_{j=1}^n2^{\gcd(i,j)}\)
\(a,b\)不为\(0\),这里要\(a,b\)同时约一个\(\gcd(a,b)\)
注意\(a=k_t,b=k_s\),由此我们确定\(C\)就能同时推\(S,T\)
即\(\sum\limits_{i=1}^{\frac{n}{max(a,b)}}2^i\)
再把\(?\)考虑进去,首先我们知道这和顺序没有关系所以我们可以枚举个数设\(f(a,b)\)为上述的答案
设\(X\)为\(A\)串中\(?\)的数量,\(Y\)同理
即\(\sum\limits_{i=0}^X\sum\limits_{j=0}^Y(f(a+i-j,b-(X-i)+(Y-j)))\binom{X}{i}\binom{Y}{j}\)
注意到\(i-j\)是定值\(k\)
即\(\sum\limits_{k=-Y}^{X}f(a+k,b-X+Y-k)\sum\limits_{j=0}^{Y}\binom{X}{k+j}\binom{Y}{Y-j}\)
注意一下\(\sum\limits_{j=0}^Y\binom{X}{k+j}\binom{Y}{Y-j}\)
这个就相当于我们在\(X\)个中选\(k\)个再选\(j\)个,再在\(Y\)中选\(Y-j\)个
等价于\(\binom{X+Y}{Y+k}\)
然后最后还有把那个\(A=B\)的情况考虑进去,注意要把我们\(a,b=0\)的情况考虑进去
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAXN=6e5+5;
int Abs(int x)
{
return x>0?x:-x;
}
int gcd(int a,int b)
{
if(b==0)
{
return a;
}
return gcd(b,a%b);
}
int prime[MAXN];
int cnt_prime;
int u[MAXN];
int vis[MAXN];
string A,B;
void Euler()
{
vis[1]=vis[0]=1;
u[1]=1;
for(int i=2;i<=MAXN-5;i++)
{
if(!vis[i])
{
prime[++cnt_prime]=i;
u[i]=-1;
}
for(int j=1;j<=cnt_prime&&prime[j]*i<=MAXN-5;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
u[i*prime[j]]=0;
break;
}
u[i*prime[j]]=u[i]*u[prime[j]];
}
}
}
int n;
int Pow(int a,int b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int inv(int a,int p)
{
return Pow(a,p-2,p);
}
int fac[MAXN];
int inv_fac[MAXN];
int C(int n,int m)
{
if(n<m||m<0)
{
return 0;
}
if(n==m||m==0)
{
return 1;
}
int P=fac[n];
P=((long long)P*inv_fac[m])%MOD;
P=((long long)P*inv_fac[n-m])%MOD;
return P;
}
int P2[MAXN];
int P;
int f(int a,int b)
{
if((long long)a*b<0)
{
return 0;
}
if((long long)a*b==0)
{
if(a||b)
{
return 0;
}
return P;
}
int Lk=gcd(a,b);
a/=Lk;
b/=Lk;
a=Abs(a);
b=Abs(b);
int Res=0;
int Mp=(n/max(a,b));
return P2[Mp];}
int main()
{
//freopen("date.in","r",stdin);
//freopen("date.out","w",stdout);
fac[0]=1;
for(int i=1;i<=MAXN-5;i++)
{
fac[i]=((long long)fac[i-1]*i)%MOD;
}
inv_fac[MAXN-5]=inv(fac[MAXN-5],MOD);
for(int i=MAXN-5-1;i>=1;i--)
{
inv_fac[i]=((long long)inv_fac[i+1]*(i+1))%MOD;
}
for(int i=1;i<=MAXN-5;i++)
{
P2[i]=((long long)P2[i-1]+Pow(2,i,MOD))%MOD;
}
Euler();
cin>>A;
cin>>B;
scanf("%d",&n);
P=0;
for(int i=1;i<=n;i++)
{
int Rd=0;
for(int j=1;i*j<=n;j++)
{
int Po=u[j];
Po=((long long)Po*((n/i)/j))%MOD;
Po=((long long)Po*((n/i)/j))%MOD;
Rd=((long long)Rd+Po)%MOD;
}
Rd=((long long)Rd*Pow(2,i,MOD))%MOD;
//printf("%d????\n",Rd);
P=((long long)P+Rd)%MOD;
}
int a=0;
int b=0;
int X=0;
int Y=0;
for(int i=0;i<A.size();i++)
{
if(A[i]=='A')
{
a++;
}
else if(A[i]=='B')
{
b--;
}
else
{
X++;
}
}
for(int i=0;i<B.size();i++)
{
if(B[i]=='A')
{
a--;
}
else if(B[i]=='B')
{
b++;
}
else
{
Y++;
}
}
int Res=0;
for(int k=-Y;k<=X;k++)
{
Res=((long long)Res+((long long)f(a+k,b+k+Y-X)*C(X+Y,k+Y))%MOD)%MOD;
}
// printf("%d %d???\n",Res,P);
if(A.size()==B.size())
{
int Tot=0;
bool f=1;
for(int i=0;i<A.size();i++)
{
if(A[i]=='?'&&B[i]=='?')
{
Tot++;
}
else if(A[i]!='?'&&B[i]!='?')
{
if(A[i]!=B[i])
{
f=0;
break;
}
}
}
if(f)
{
int O=(((long long)P2[n]*P2[n])%MOD-P+MOD)%MOD;
O=((long long)O*Pow(2,Tot,MOD))%MOD;
Res=((long long)Res+O)%MOD;
}
}
printf("%d",Res);
}
[CEOI2020] 星际迷航
我们先考虑一棵树的结果
考虑维护以每个点为根是否必胜,这个是可以用换根\(Dp\)做
\(Dp=0\)为必败点,\(=1\)为必胜点
再考虑\(D=1\)怎么办
我们考虑\(i\)这个点连到必胜点,很明显\(i\)的胜负是不会变的,连必败点,如果是\(i\)自己是必败点他就会变必胜
而\(i\)的变化可能会引起根节点的变化,我们记录\(K_i\)为\(i\)为根子树内会引起变化点的数量,同样是换根
如果我们统计一下标号为\(1\)的树的必败点和必胜点数量\(f_0,f_1\)
那如果\(Dp_1=1\)答案即为\(f_0(n-K_1)+f_1n\),否则为\(f_0K_1\)
注意到跳到另一棵树后根会变,所以记录全部的\(K\)是有必要的
同样\(f_0,f_1\)为处理了后\(D\)棵树后的数量
不难想到从当前树连\(f_0\)为根本身就是败点接胜点和接败不变加胜接败变
\(f1\)同理,这个矩阵维护即可
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
const int MOD=1e9+7;
int n;
int x,y;
long long D;
vector<int>g[MAXN];
struct Martix{
int val[2][2];
int n,m;
void clear()
{
memset(val,0,sizeof(val));
}
void init()
{
clear();
for(int i=0;i<n;i++)
{
val[i][i]=1;
}
}
Martix operator*(const Martix x)const{
Martix Res;
Res.clear();
Res.n=n;
Res.m=x.m;
for(int k=0;k<m;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<x.m;j++)
{
Res.val[i][j]=((long long)Res.val[i][j]+((long long)val[i][k]*x.val[k][j])%MOD)%MOD;
}
}
}
return Res;
}
}A,B;
Martix Pw(Martix a,long long b)
{
Martix res;
res.n=a.n;
res.m=a.m;
res.init();
Martix Base=a;
while(b)
{
if(b&1)
{
res=(res*Base);
}
Base=(Base*Base);
b>>=1;
}
return res;
}
int Pow(int a,long long b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int dp1[MAXN];
int dp2[MAXN];
int Fa[MAXN];
int King[MAXN];
int Queen[MAXN];
void dfs1(int x,int f)
{
Fa[x]=f;
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
dfs1(v,x);
}
int ct=0;
int Key;
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
if(!dp1[v])
{
ct++;
Key=v;
dp1[x]=1;
}
}
if(dp1[x])
{
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
if(ct==1&&Key==v)
{
King[x]+=(King[v]);
}
}
}
else
{
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
King[x]+=(King[v]);
}
}
if(!dp1[x])
{
King[x]++;
}
}
set<int>S;
void dfs2(int x,int f)
{
S.clear();
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
if(!dp1[v])
{
S.insert(v);
}
}
if(!dp2[x])
{
S.insert(x);
}
int Rp=0;
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
if(dp1[v])
{
Rp+=King[v];
}
}
if(dp2[x])
{
Rp+=Queen[x];
}
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
if(!dp1[v])
{
S.erase(v);
}
if((S.size()))
{
dp2[v]=1;
if(S.size()==1)
{
int P=(*(S.begin()));
if(P==x)
{
Queen[v]=Queen[x];
}
else
{
Queen[v]=King[P];
}
}
else
{
Queen[v]=0;
}
}
else
{
dp2[v]=0;
int Pop=Rp;
if(dp1[v])
{
Pop-=King[v];
}
Queen[v]=Pop;
}
if(!dp2[v])
{
Queen[v]++;
}
if(!dp1[v])
{
S.insert(v);
}
}
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
dfs2(v,x);
}
}
int Dp[MAXN];
int aL[MAXN];
int main()
{
//freopen("input22.txt","r",stdin);
//freopen("tree.out","w",stdout);
dp2[1]=1;
scanf("%d %lld",&n,&D);
for(int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++)
{
int ct=0;
int Key;
for(int j=0;j<g[i].size();j++)
{
int v=g[i][j];
if(v==Fa[i])
{
continue;
}
if(!dp1[v])
{
ct++;
Key=v;
Dp[i]=1;
}
}
if(!dp2[i])
{
Key=Fa[i];
ct++;
Dp[i]=1;
}
if(Dp[i]==1)
{
for(int j=0;j<g[i].size();j++)
{
int v=g[i][j];
if(v==Fa[i])
{
continue;
}
if(v==Key&&ct==1)
{
aL[i]=King[v];
}
}
if(Key==Fa[i]&&ct==1)
{
aL[i]=Queen[i];
}
}
else
{
for(int j=0;j<g[i].size();j++)
{
int v=g[i][j];
if(v==Fa[i])
{
continue;
}
aL[i]+=King[v];
}
aL[i]+=Queen[i];
}
if(!Dp[i])
{
aL[i]++;
}
}
for(int i=1;i<=n;i++)
{
dp2[i]=Dp[i];
Queen[i]=aL[i];
}
int f0=0;
int f1=0;
for(int i=1;i<=n;i++)
{
if(dp2[i])
{
f1++;
}
else
{
f0++;
}
}
//printf("%d %d??\n",f0,f1);
A.n=1;
A.m=2;
A.clear();
A.val[0][0]=f0;
A.val[0][1]=f1;
B.n=2;
B.m=2;
B.clear();
for(int i=1;i<=n;i++)
{
if(dp2[i]==1)
{
B.val[0][0]=((long long)B.val[0][0]+Queen[i])%MOD;
}
else
{
B.val[0][0]=((long long)B.val[0][0]+(n-Queen[i]))%MOD;
}
}
for(int i=1;i<=n;i++)
{
if(dp2[i]==0)
{
B.val[1][0]=((long long)B.val[1][0]+n)%MOD;
}
}
for(int i=1;i<=n;i++)
{
if(dp2[i]==0)
{
B.val[0][1]=((long long)B.val[0][1]+Queen[i])%MOD;
}
else
{
B.val[0][1]=((long long)B.val[0][1]+(n-Queen[i]))%MOD;
}
}
for(int i=1;i<=n;i++)
{
if(dp2[i]==1)
{
B.val[1][1]=((long long)B.val[1][1]+n)%MOD;
}
}
B=Pw(B,D-1);
A=A*B;
if(dp1[1])
{
int Po=(n-Queen[1]);
Po=((long long)Po*A.val[0][0])%MOD;
int Qo=(n);
Qo=((long long)Qo*A.val[0][1])%MOD;
Po=((long long)Po+Qo)%MOD;
printf("%d",Po);
}
else
{
int Po=(Queen[1]);
Po=((long long)Po*A.val[0][0])%MOD;
printf("%d\n",Po);
}
}
[THUWC2017]随机二分图
首先转化一下题意
这里的期望相当于是每个图出现的概率\(\times\)图上完美匹配的数量
当然我们可以将其转化为每个匹配出现的概率之和,这里其他不是匹配的边是无影响的
然后这个其实就是每条边出现概率的乘积
我们先考虑只有\(t=0\)的情况
不难想到我们设\(dp_{i,S}\)表示左部选了前\(i\)个点,右部选了\(S\)这个点集是所有完美匹配的概率乘积之和
很明显\(dp_{i+1,S|j}=\sum dp_{i,S}P_{i,j}\)
注意这里是不会算重的,因为已经钦定了顺序
考虑\(t>0\)的情况
对于\(t=1\),如果这两条边只有一条在匹配中,那我们可以直接把两条边独立地\(1/2\)的概率相连
如果都在匹配中,那原来的方法只会有\(1/4\)的贡献,但实际上是\(1/2\),我们要再加一条一次性连两条边的并有\(1/4\)的贡献
注意这里两条边的左部的点变化是不连续的,所以左部同样需要状压,这里就又涉及到转移顺序的问题,这里肯定不能随便连边,所以这里我们每次强制连\(S\)的最小位
解释一下为什么连两条边的操作是\(1/4\)且是直接加上去的,我们考虑\(S\)去除两条边的点,这里有两种途径,一是一条边一条边地走,还有就是直接连两条,这两个互不干扰
\(t=2\)的情况类似
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int Pow(int a,int b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int inv(int a,int p)
{
return Pow(a,p-2,p);
}
int inv2,inv4;
int n,m,q;
int S[1005];
int P[1005];
map<int,int>dp;
int t,a,b,x,y;
int dfs(int x)
{
if(dp.find(x)!=dp.end())
{
return dp[x];
}
if(!x)
{
return 1;
}
int Key;
for(int i=0;i<n;i++)
{
if((x>>i)&1)
{
Key=i;
break;
}
}
int Res=0;
for(int i=1;i<=m;i++)
{
if(!(S[i]&(1<<Key)))
{
continue;
}
if((S[i]&x)!=S[i])
{
continue;
}
Res=((long long)Res+((long long)dfs(x^S[i])*P[i])%MOD)%MOD;
}
dp[x]=Res;
return Res;
}
int main()
{
//freopen("date.in","r",stdin);
inv2=inv(2,MOD);
inv4=inv(4,MOD);
scanf("%d %d",&n,&q);
m=0;
for(int i=1;i<=q;i++)
{
scanf("%d",&t);
if(t==0)
{
scanf("%d %d",&a,&b);
S[++m]=(1<<(a-1)|(1<<(n+b-1)));
P[m]=(inv2);
}
else
{
scanf("%d %d %d %d",&a,&b,&x,&y);
S[++m]=(1<<(a-1)|(1<<(n+b-1)));
P[m]=(inv2);
S[++m]=(1<<(x-1)|(1<<(n+y-1)));
P[m]=(inv2);
if((S[m]&S[m-1]))
{
continue;
}
++m;
S[m]=S[m-1]|S[m-2];
P[m]=inv4;
if(t==2)
{
P[m]=(MOD-P[m]);
}
}
}
int Res=dfs((1<<2*n)-1);
printf("%lld",((long long)Res*(1<<n)%MOD));
}
[HNOI2014]江南乐
首先明确\(SG\)函数的意义是将游戏等价于\(SG(x)\)个石子的\(Nim\)堆
考虑在这个游戏,很明显是可以将其拆分为每个石子的\(SG\)的异或和
对于一个\(x\)的石子堆,我们考虑分成\(M\)堆,其中大小有\(a=\lfloor\dfrac{x}{M}\rfloor,a+1\),个数分别为\(num=M-(x\bmod M),M-num\)
注意多个游戏的\(SG\)为其子游戏\(SG\)的异或和,则分成\(M\)堆对应的\(SG\)即为\([SG(a)*(num\&1)]\oplus [SG(a+1)*((M-num)\&1)]\)
最后再对每种分法取\(MEX\)即可,这样做是\(O(n^3)\)的
打个表发现答案不超过\(20\),这里我们可以考虑每个\(a\)对\(x\)的影响,即用\(aM\)倒退\(x\)的子结点是否有\(SG(a)\)
注意\(M>1\),这启示我们直接倍增
直接对于\(a\)调和级数枚举\(M\),考虑计算出其影响的区间及其长度,推导一下不难发现我们把位置分奇偶算贡献即可,最后离线处理一下即可,时间复杂度应该是\(O(nlog^2n)\)的
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
const int Lit=1e5;
int T;
int n,F;
int x;
int dp[MAXN];
vector<pair<int,int> >Rp[MAXN];
int Odd[25];
int Even[25];
int main()
{
// freopen("date.in","r",stdin);
// freopen("date.out","w",stdout);
scanf("%d %d",&T,&F);
dp[1]=0;
for(int Len=2;Len/2<=Lit;Len<<=1)
{
int L=1;
int R=min(Len,Lit);
int Pr=Len/2;
for(int i=L;i<=R;i++)
{
Rp[i].clear();
}
for(int i=1;i<=Pr;i++)
{
for(int j=2;i*j<=R;j++)
{
int l=i*j;
int r=(i+1)*j-1;
int len=(r-l+1);
if(len%2==0)
{
Rp[l+1].push_back(make_pair(dp[i]^dp[i+1],r));
}
else
{
Rp[l].push_back(make_pair(dp[i],r));
Rp[l+1].push_back(make_pair(dp[i+1],r));
}
}
}
for(int i=1;i<=20;i++)
{
Odd[i]=0;
Even[i]=0;
}
for(int i=1;i<=Lit;i++)
{
if(i&1)
{
for(int j=0;j<Rp[i].size();j++)
{
Odd[Rp[i][j].first]=max(Odd[Rp[i][j].first],Rp[i][j].second);
}
if(i>Pr)
{
for(int j=1;j<=20;j++)
{
if(Odd[j]>=i)
{
}
else
{
if(i>=F)
{
dp[i]=j;
if(i==1)
{
dp[i]=0;
}
}
else
{
dp[i]=0;
}
break;
}
}
}
}
else
{
for(int j=0;j<Rp[i].size();j++)
{
Even[Rp[i][j].first]=max(Even[Rp[i][j].first],Rp[i][j].second);
}
if(i>Pr)
{
for(int j=1;j<=20;j++)
{
if(Even[j]>=i)
{
}
else
{
if(i>=F)
{
dp[i]=j;
if(i==1)
{
dp[i]=0;
}
}
else
{
dp[i]=0;
}
break;
}
}
}
}
}
}
// for(int i=1;i<=10;i++)
// {
// printf("%d\n",dp[i]);
// }
while(T--)
{
scanf("%d",&n);
int Res=0;
while(n--)
{
scanf("%d",&x);
Res=Res^dp[x];
}
if(Res)
{
printf("1 ");
}
else
{
printf("0 ");
}
}
}
[HNOI2007]分裂游戏
一个很巧妙的题
首先每次操作是独立的,因为我们可以考虑对于\(i\)有\(1\)个豆子,操作\(i,j,k\)之后将其拆分成\(j,k\)两个游戏
假设\(i\)有一个豆子,胜负的决定在于\(i\)与\(n\)的位置,我们不妨倒序
很明显是\(SG\)函数,后续状态即使\(j,k\)的合并也就是\(SG(j)\oplus SG(k)\)
这里我们可以\(O(n^3)\)求出\(SG\)
对于提供方案,我们可以直接枚举,寻找\(i,j,k\),考虑操作后\(i,j,k\)的贡献情况都变了,于是直接异或即可
Show Code
#include<bits/stdc++.h>
using namespace std;
int T;
int n;
int dp[55];
int a[55];
int main()
{
freopen("date.in","r",stdin);
freopen("date.out","w",stdout);
dp[1]=0;
for(int i=2;i<=50;i++)
{
vector<int>Rp;
for(int j=1;j<i;j++)
{
for(int k=1;k<=j;k++)
{
Rp.push_back(dp[j]^dp[k]);
}
}
sort(Rp.begin(),Rp.end());
unique(Rp.begin(),Rp.end());
int Key=Rp.size();
for(int j=0;j<Rp.size();j++)
{
if(Rp[j]!=j)
{
Key=j;
break;
}
}
dp[i]=Key;
}
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int Res=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
int Rlf=n-i+1;
if(a[i]&1)
{
Res^=dp[Rlf];
}
}
if(Res)
{
int Tot=0;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
for(int k=j;k<=n;k++)
{
int Lp=Res;
Lp^=dp[n-i+1];
Lp^=dp[n-j+1];
Lp^=dp[n-k+1];
if(Lp==0)
{
if(!Tot)
{
printf("%d %d %d\n",i-1,j-1,k-1);
}
Tot++;
}
}
}
}
printf("%d\n",Tot);
}
else
{
printf("-1 -1 -1\n");
printf("0\n");
}
}
}
[THUSCH2017] 巧克力
一个有意思的题
首先解决第一问,这个选择一些联通块使其包含\(k\)中不同的颜色看着就很像斯坦纳树,我们只需要枚举一些关键颜色即可
当然,枚举关键颜色什么的就已经超时了
这里我们可以考虑给每个颜色都赋一个\((0,k)\)的权,用这个跑斯坦纳树
合理跑出来的答案只会偏大,假设我们随机的权是的答案的方案正好不同,很明显,这样的正确率\(T=\dfrac{k!}{k^k}\)
这个正确率约为\(0.03\),如果我们多跑几次,错误的概率即为\((1-T)^t\)
实测\(t=150\)就可以了
考虑第二问,套路二分,在跑斯坦纳树的时候给大于\(Mid\)赋\(1\),小于的赋\(-1\),记录二元组的最小值即可
Show Code
#include<bits/stdc++.h>
using namespace std;
mt19937 mt_rand(time(0));
int n,m;
int K;
int C[240][240];
int clo[240];
int c[240];
int Hash(int x,int y)
{
return (x-1)*m+y;
}
int zfx[5]={0,0,1,-1};
int zfy[5]={1,-1,0,0};
int Val[240];
int a[240];
int A[240][240];
pair<int,int>dp[(1<<5)+5][240];
vector<int>g[240];
struct Node{
int u;
pair<int,int>Val;
bool operator < (const Node x)const{
return Val>x.Val;
}
};
pair<int,int>Plus(pair<int,int>x,pair<int,int>y)
{
return make_pair(x.first+y.first,x.second+y.second);
}
pair<int,int>Sub(pair<int,int>x,pair<int,int>y)
{
return make_pair(x.first-y.first,x.second-y.second);
}
priority_queue<Node>q;
bool vis[240];
void dijkstra(int S)
{
memset(vis,0,sizeof(vis));
while(q.size())
{
Node temp=q.top();
q.pop();
if(vis[temp.u])
{
continue;
}
vis[temp.u]=1;
for(int i=0;i<g[temp.u].size();i++)
{
int v=g[temp.u][i];
if(dp[S][v]>Plus(dp[S][temp.u],make_pair(1,Val[v])))
{
dp[S][v]=Plus(dp[S][temp.u],make_pair(1,Val[v]));
Node od;
od.u=v;
od.Val=dp[S][v];
q.push(od);
}
}
}
}
pair<int,int>Get()
{
int T=150;
pair<int,int>Res;
Res.first=0x3f3f3f3f;
Res.second=0x3f3f3f3f;
while(T--)
{
for(int i=1;i<=n*m;i++)
{
clo[i]=i;
}
shuffle(clo + 1, clo+ n * m, mt_rand);
for(int i=1;i<=n*m;i++)
{
clo[i]=(clo[i]%K);
}
for(int S=0;S<(1<<K);S++)
{
for(int i=1;i<=n*m;i++)
{
dp[S][i].first=0x3f3f3f3f;
dp[S][i].second=0x3f3f3f3f;
}
}
for(int i=1;i<=n*m;i++)
{
if(c[i]!=-1)
{
dp[(1<<clo[c[i]])][i].first=1;
dp[(1<<clo[c[i]])][i].second=Val[i];
}
}
for(int S=0;S<(1<<K);S++)
{
while(q.size())
{
q.pop();
}
for(int i=1;i<=n*m;i++)
{
for(int sub=(S&(S-1));sub;sub=(S)&(sub-1))
{
dp[S][i]=min(dp[S][i],Plus(dp[sub][i],Sub(dp[S^sub][i],make_pair(1,Val[i]))));
}
if(dp[S][i].first<0x3f3f3f3f)
{
Node lsp;
lsp.u=i;
lsp.Val=dp[S][i];
q.push(lsp);
}
}
dijkstra(S);
}
for(int i=1;i<=n*m;i++)
{
Res=min(Res,dp[(1<<K)-1][i]);
}
}
return Res;
}
bool check(int x)
{
for(int i=1;i<=n*m;i++)
{
if(a[i]>x)
{
Val[i]=1;
}
else
{
Val[i]=-1;
}
}
pair<int,int>P=Get();
return P.second<=0;
}
int main()
{
//freopen("date.in","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d %d",&n,&m,&K);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&C[i][j]);
c[Hash(i,j)]=C[i][j];
}
}
vector<int>Pro;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&A[i][j]);
a[Hash(i,j)]=A[i][j];
Pro.push_back(A[i][j]);
}
}
sort(Pro.begin(),Pro.end());
unique(Pro.begin(),Pro.end());
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
g[Hash(i,j)].clear();
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<4;k++)
{
int nx=i+zfx[k];
int ny=j+zfy[k];
if(nx>=1&&ny>=1&&nx<=n&&ny<=m)
{
if(c[Hash(nx,ny)]==-1)
{
continue;
}
g[Hash(i,j)].push_back(Hash(nx,ny));
}
}
}
}
pair<int,int>Pk=Get();
if(Pk.first==0x3f3f3f3f)
{
printf("-1 -1\n");
continue;
}
printf("%d ",Pk.first);
int l=0;
int r=Pro.size()-1;
int Key;
int Tot=0;
while(l<=r)
{
++Tot;
int mid=(l+r)>>1;
if(check(Pro[mid]))
{
r=mid-1;
Key=mid;
}
else
{
l=mid+1;
}
}
printf("%d\n",Pro[Key]);
}
}
[THUSCH2017] 杜老师
先明确一些与线性基有关的性质
首先,我们对于线性空间\(V\)有线性基\(B\),这里\(V\)中所有元素都能用\(B\)中的元素互相异或得到
考虑\(span(V)\)中的一个元素\(x\),\(x\)是\(2^{|B|}\)中的一个
由于\(B\)是\(V\)的线性基,因此\(x\)的异或至少有\(2^{|V|-|B|}\)种,也就是我们考虑\(V-B\)中的随便选,选出来后我们考虑用\(B\)来凑成\(x\),注意这里对于每一个\(x\)都至少有\(2^{|V|-|B|}\),共有\(2^{|B|}\)个\(x\),因此每个\(x\)的方案都因取下界
回到本题,这里的凑一个集合使得乘积为完全平方数,这不难让我们想到用线性基维护质数集合,最后求线性组合为\(0\)的方案,如果用\(bitset\)优化
这里时间复杂度为\(T\dfrac{\pi^2(n)n}{w}\)
再考虑优化,我们发现一些质因子只会在一些数里出现一次,具体的\(>\sqrt{(n)}\)都只会出现一次,这里我们可以将其先作为最高位维护,至于\(<\sqrt{(n)}\)的正常维护即可
时间复杂度为\(T\dfrac{n\pi(n)}{w}\)
这里我们还会发现如果区间长度过大,每个质因子好像都会单独作为一维,也就是线性空间被填满了,对于长度过大的直接枚举质因子即可
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e7+5;
const int MOD=998244353;
int Pow(int a,int b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int Lit;
int S;
int Id[MAXN];
map<int,bitset<455> >vis;
int Vis[MAXN];
int prime[MAXN];
int cnt_prime;
void Euler()
{
Vis[0]=Vis[1]=1;
for(int i=2;i<=MAXN-5;i++)
{
if(!Vis[i])
{
prime[++cnt_prime]=i;
Id[i]=cnt_prime;
}
for(int j=1;j<=cnt_prime&&prime[j]*i<=MAXN-5;j++)
{
Vis[prime[j]*i]=1;
if(i%prime[j]==0)
{
break;
}
}
}
}
void Insert(int x)
{
int Now=x;
bitset<455>P;
for(int i=1;i<=450&&prime[i]*prime[i]<=Now;i++)
{
if(Now%prime[i]==0)
{
int Tot=0;
while(Now%prime[i]==0)
{
++Tot;
Now/=prime[i];
}
if(Tot&1)
{
P.set(i);
}
}
}
if(Now>1)
{
if(Now<=Lit)
{
P.set(Id[Now]);
}
else
{
if(vis.find(Now)!=vis.end())
{
P^=vis[Now];
}
else
{
S++;
vis[Now]=P;
return;
}
}
}
for(int i=450;i>=1;i--)
{
if(P[i]!=1)
{
continue;
}
if(vis.find(i)!=vis.end())
{
P^=vis[i];
}
else
{
vis[i]=P;
S++;
break;
}
}
}
int T;
int l,r;
int main()
{
//freopen("date.in","r",stdin);
Euler();
Lit=prime[450];
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&l,&r);
if(r-l+1>=7000)
{
int Cnt=0;
for(int i=1;i<=cnt_prime&&prime[i]<=r;i++)
{
if(((l-1)/prime[i])!=((r)/prime[i]))
{
Cnt++;
}
}
printf("%d\n",Pow(2,r-l+1-Cnt,MOD));
continue;
}
vis.clear();
S=0;
for(int i=l;i<=r;i++)
{
Insert(i);
}
//printf("%d??\n",S);
printf("%d\n",Pow(2,r-l+1-S,MOD));
}
}
[THUSCH2017] 换桌
线段树加费用流
建图也不难,建\(m\)棵线段树,区间连边就是注意走到一个桌子后连一个环来表示第二种费用即可
Show Code
#include<bits/stdc++.h>
int Abs(int x)
{
return x>0?x:-x;
}
#define INF 1e9
#define ls 2*p
#define rs 2*p+1
using namespace std;
const int MAXM=1e5+5;
const int MAXN=1e5+5;
int n,m;
int L[1005][1005];
int R[1005][1005];
struct Edge {
int to, nex;
int val;
int cost;
} edge[MAXM * 2];
int cnt = 1;
int head[MAXN];
void Add(int u, int v, int w, int cost) {
edge[++cnt].to = v;
edge[cnt].nex = head[u];
edge[cnt].val = w;
edge[cnt].cost = cost;
head[u] = cnt;
}
void add(int u,int v,int w,int cost)
{
Add(u,v,w,cost);
Add(v,u,0,-cost);
}
int dis[MAXN];
int vis[MAXN];
int arc[MAXN];
int S,T;
bool spfa() {
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
queue<int>q;
q.push(S);
dis[S] = 0;
vis[S] = 1;
while (q.size()) {
int temp = q.front();
q.pop();
vis[temp] = 0;
arc[temp] = head[temp];
for (int i = head[temp]; i; i = edge[i].nex) {
int v = edge[i].to;
int w = edge[i].cost;
if (!edge[i].val) {
continue;
}
if (dis[v] > dis[temp] + w) {
dis[v] = dis[temp] + w;
if (!vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
}
memset(vis, 0, sizeof(vis));
if (dis[T] == 0x3f3f3f3f) {
return 0;
}
return 1;
}
int mincost = 0;
int maxflow = 0;
int dfs(int x, int flow) {
if (x == T || flow == 0) {
return flow;
}
int used = 0;
vis[x] = 1;
for (int i = arc[x]; i; i = edge[i].nex) {
int v = edge[i].to;
int w = edge[i].cost;
if (vis[v]) {
continue;
}
if (!edge[i].val) {
continue;
}
if (dis[x] + w == dis[v]) {
arc[x] = i;
int nowu = dfs(v, min(flow - used, edge[i].val));
used += nowu;
edge[i].val -= nowu;
edge[i ^ 1].val += nowu;
mincost += nowu * w;
if (used == flow) {
break;
}
}
}
vis[x] = 0;
return used;
}
pair<int, int>MCMF() {
maxflow = 0;
mincost = 0;
int flow = 0;
while (spfa()) {
maxflow += dfs(S, INF);
}
return make_pair(maxflow, mincost);
}
int cnt_node;
struct Seg_node{
int l,r;
int ind;
};
struct Seg{
int Type;
Seg_node Tree[5005];
void Build(int p,int l,int r)
{
Tree[p].l=l;
Tree[p].r=r;
Tree[p].ind=++cnt_node;
if(l==r)
{
return;
}
int mid=(l+r)>>1;
Build(ls,l,mid);
Build(rs,mid+1,r);
add(Tree[p].ind,Tree[ls].ind,INF,0);
add(Tree[p].ind,Tree[rs].ind,INF,0);
}
void Put(int p)
{
if(Tree[p].l==Tree[p].r)
{
if(Type<=m)
{
int I=Tree[p].l;
add(Tree[p].ind,cnt_node+(I-1)*m+Type,INF,2*I);
}
else
{
int I=Tree[p].l;
add(Tree[p].ind,cnt_node+(I-1)*m+(Type-m),INF,-2*I);
}
return;
}
Put(ls);
Put(rs);
}
void Update(int p,int l,int r,int P,int Cost)
{
if(l>r)
{
return;
}
if(Tree[p].l>=l&&Tree[p].r<=r)
{
add(P,Tree[p].ind,INF,Cost);
return;
}
int mid=(Tree[p].l+Tree[p].r)>>1;
if(l<=mid)
{
Update(ls,l,r,P,Cost);
}
if(r>mid)
{
Update(rs,l,r,P,Cost);
}
}
}t[35];
int main()
{
//freopen("date.in","r",stdin);
//freopen("date.out","w",stdout);
scanf("%d %d",&n,&m);
S=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&L[i][j]);
L[i][j]++;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&R[i][j]);
R[i][j]++;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
add(0,(i-1)*m+j,1,0);
}
}
cnt_node=n*m;
for(int i=1;i<=2*m;i++)
{
t[i].Build(1,1,n);
t[i].Type=i;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
t[j].Update(1,max(i,L[i][j]),R[i][j],(i-1)*m+j,-2*i);
t[j+m].Update(1,L[i][j],min(R[i][j],i),(i-1)*m+j,2*i);
}
}
for(int i=1;i<=2*m;i++)
{
t[i].Put(1);
}
T=cnt_node+(n*m+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<m;j++)
{
int Np=cnt_node+(i-1)*m+j;
int Nt=cnt_node+(i-1)*m+j+1;
add(Np,Nt,INF,1);
}
int eNp=cnt_node+(i-1)*m+m;
int eNt=cnt_node+(i-1)*m+1;
add(eNp,eNt,INF,1);
for(int j=2;j<=m;j++)
{
int Np=cnt_node+(i-1)*m+j;
int Nt=cnt_node+(i-1)*m+j-1;
add(Np,Nt,INF,1);
}
eNp=cnt_node+(i-1)*m+m;
eNt=cnt_node+(i-1)*m+1;
add(eNt,eNp,INF,1);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
add(cnt_node+(i-1)*m+j,T,1,0);
}
}
//printf("%d??\n",cnt_node);
pair<int,int>Pk=MCMF();
if(Pk.first!=n*m)
{
printf("no solution\n");
}
else
{
printf("%d\n",Pk.second);
}
}
[PKUWC2018]斗地主
首先分个类,把春天牌分为农民没炸弹和有
如果没有,那地主牌必须有所有牌,最后剩下的\(6\)张直接搜就可以了
至于判断是否为春天牌,也是直接搜,注意一次只打一种牌即可
如果有,那地主一定除了炸一定能一次出完,且炸也一定大于农民的,直接搜
[PKUWC2018]随机算法
很明显这里如果确定当前选择的\(S\),那他的最大独立集是可以求出来的
然后这里我们不妨设\(f_S\)为选择了点集\(S\)的正确概率
考虑枚举\(S\)的一点\(i\),我们有\(\dfrac{1}{|S|}\)的概率最后选到它作为独立集
考虑是怎么加的,我们肯定是将\(S\)中与\(i\)相连的点放在\(i\)后选,然后他们直接的顺序无所谓
如果我们设\(S\)剔除\(i\)及与他相连的点为\(T\),那\(i\)的贡献即为\(f_T\)
至于我们为什么不计算不作为独立集点的贡献,因为这样已经将其统计
时间复杂度\(O(2^nn)\)
Show Code
using namespace std;
const int MOD=998244353;
int a[25][25];
int Sta[25];
int Siz[(1<<20)+5];
int dp[(1<<20)+5];
int Pow(int a,int b,int p)
{
int res=1;
int base=a;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int inv(int a,int p)
{
return Pow(a,p-2,p);
}
int x,y;
int n,m;
int main()
{
//freopen("date.in","r",stdin);
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
a[x][y]=1;
a[y][x]=1;
}
for(int i=1;i<=n;i++)
{
int S=(1<<n)-1;
for(int j=1;j<=n;j++)
{
if(a[i][j])
{
S^=(1<<(j-1));
}
}
Sta[i]=S;
}
for(int S=1;S<(1<<n);S++)
{
for(int i=1;i<=n;i++)
{
if((S&(1<<(i-1))))
{
Siz[S]=max(Siz[S],Siz[(S&Sta[i])^(1<<(i-1))]+1);
}
}
}
dp[0]=1;
for(int S=1;S<(1<<n);S++)
{
int Cnt=0;
for(int i=1;i<=n;i++)
{
if((S&(1<<(i-1))))
{
Cnt++;
}
else
{
continue;
}
int T=(S&Sta[i])^(1<<(i-1));
if(Siz[T]==Siz[S]-1)
{
dp[S]=((long long)dp[S]+dp[T])%MOD;
}
}
dp[S]=((long long)dp[S]*inv(Cnt,MOD))%MOD;
}
printf("%d\n",dp[(1<<n)-1]);
}
[PKUWC2018]随机游走
首先明确\(Min-Max\)容斥
\(Max(S)=\sum\limits_{T\subseteq S}{(-1)^{|T|+1}}Min(T)\)
同时,\(E(Max(S))=\sum\limits_{T\subseteq S}{(-1)^{|T|+1}}E(Min(T))\),也是成立的
回到本题,我们如果把经过的点算一个时间戳,这里我们就可以把第一次经过所有关键点的时间转化为第一次到达关键点
至于解决第一次到达,类似于\(Maze\)的处理,将每个点用父亲的\(dp\)和常数表示
\(dp_x=\sum\dfrac{dp_{son}}{d_x}+\dfrac{dp_f}{d_x}+1\)
设\(dp_x=a_xdp_f+b_x\)
\(dp_x=\sum\dfrac{a_{son}dp_x+b_{son}}{d_x}+\dfrac{dp_f}{d_x}+1\)
\((\sum\dfrac{a_{son}}{d_x}-1)dp_x+\sum\dfrac{b_{son}}{d_x}+\dfrac{dp_f}{d_x}+1=0\)
\(a_x=\dfrac{1}{d_x(1-\sum\dfrac{a_{son}}{d_x})},b_x=\dfrac{\sum\dfrac{b_{son}}{d_x}+1}{(1-\sum\dfrac{a_{son}}{d_x})}\)
紧接着,回到\(Max(S)=\sum\limits_{T\subseteq S}{(-1)^{|T|+1}}Min(T)\)
可以发现这很想子集卷积,高维前缀和即可
Show Code
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
int Pow(int a,int b,int p)
{
int base=a;
int res=1;
while(b)
{
if(b&1)
{
res=((long long)res*base)%p;
}
base=((long long)base*base)%p;
b>>=1;
}
return res;
}
int Inv[25];
int inv(int a,int p)
{
if(a<=20)
{
if(!Inv[a])
{
return Pow(a,p-2,p);
}
return Inv[a];
}
else
{
return Pow(a,p-2,p);
}
}
int n,X,Q;
int x,y;
vector<int>g[20];
int d[20];
int Mark[20];
int dpf[20];
int dpx[20];
int Yp;
void dfs(int x,int f)
{
if(Mark[x])
{
return;
}
int Suma=0;
int Sumb=0;
for(int i=0;i<g[x].size();i++)
{
int v=g[x][i];
if(v==f)
{
continue;
}
dfs(v,x);
Suma=((long long)Suma+dpf[v])%MOD;
Sumb=((long long)Sumb+dpx[v])%MOD;
}
Suma=((long long)Suma*inv(d[x],MOD))%MOD;
Sumb=((long long)Sumb*inv(d[x],MOD))%MOD;
dpf[x]=inv(((((long long)1-Suma+MOD)%MOD)*(d[x]))%MOD,MOD);
dpx[x]=inv((((long long)1-Suma+MOD)%MOD),MOD);
dpx[x]=((long long)dpx[x]*(((long long)Sumb+1)%MOD))%MOD;
}
int Get(int S)
{
if(S==0)
{
return 0;
}
for(int i=1;i<=n;i++)
{
Mark[i]=0;
dpf[i]=0;
dpx[i]=0;
if((S>>(i-1))&1)
{
Mark[i]=1;
}
}
Yp=S;
dfs(X,0);
return dpx[X];
}
int A[(1<<18)+5];
int main()
{
//freopen("date.in","r",stdin);
//freopen("date.out","w",stdout);
scanf("%d %d %d",&n,&Q,&X);
for(int i=1;i<=20;i++)
{
Inv[i]=inv(i,MOD);
}
for(int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
g[x].push_back(y);
d[x]++;
d[y]++;
g[y].push_back(x);
}
for(int S=0;S<(1<<n);S++)
{
int Kp=Get(S);
int Cp=0;
for(int i=1;i<=n;i++)
{
if((S>>(i-1))&1)
{
Cp++;
}
}
Cp++;
// printf("%d %d\n",S,Kp);
if(Cp&1)
{
Kp=(MOD-Kp);
}
A[S]=Kp;
}
for(int i=1;i<=n;i++)
{
for(int S=0;S<(1<<n);S++)
{
if((S&(1<<(i-1))))
{
A[S]=((long long)A[S]+A[S^(1<<(i-1))])%MOD;
}
}
}
while(Q--)
{
int Num;
scanf("%d",&Num);
int S=0;
while(Num--)
{
scanf("%d",&x);
S|=(1<<(x-1));
}
printf("%d\n",A[S]);
}
}