[考试反思]0328省选模拟56:漂浮
考场上预估得分是$70+50+100=220$
然后居然能炸成这个鬼德行。。。
最近状态一直不好,今天难得大概是想到了两个正解,但是三题全挂细节。。。
$T1$题目条件读丢了一个,少了$20pts$(读题出锅这咋治。。。)
$T2$一眼看出是容斥但没时间细想少了一条特判把$50$的暴力也送了。
$T3$没多少时间但是发现了规律然而变量名写错除了无解以外的$90$全部暴毙。
好像是时间分配出问题了。完全没时间写对拍。
最后一题连想带写只用了半小时,不出错才奇怪。
大概还是应该至少稳住一道题吧。至少不会像今天这么惨。
T1:取石子游戏
大意:$n$个数,要求取出$k(k<n)$个,满足$k$是给定常数$d$的倍数,使得剩下的数异或i和为$0$。$n \le 5 \times 10^5,a_i \le 10^6,\sum a_i \le 10^7 d \le 10$
坑点是$k<n$,不能相等,要特判。
最朴素的就是做一个类似模意义下的背包,复杂度$O(max(a)nd)$
我最开始写的是从小到大加入,背包的值域不断扩大到当前最大物品的$2^{highbit(a)+1}$。然而写丑了变成$O(\sum a_i d^2)$的了。
然后如果按照从大到小加入物品的话,那么当$highbit(a_{i-1}) \neq highbit(a_i)$时,我们就知道此时最高位不对的状态以后也再也达不到,直接舍弃。
这样然后玄学的算一下复杂度,大约是$O(\sum a_i d)$的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 1000000007 4 #define S 1048576 5 int dp[10][S],t[10][S],n,c[S],d,v,b=1,fac[S],inv[S]; 6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 7 int read(){ 8 register int p=0;register char ch=getchar(); 9 while(!isdigit(ch))ch=getchar(); 10 while(isdigit(ch))p=(p<<3)+(p<<1)+ch-48,ch=getchar(); 11 return p; 12 } 13 void add(int&a,int b){a+=b;if(a>=mod)a-=mod;} 14 void ctb(int n){ 15 while(b<=n)b<<=1; 16 while(b>n)b>>=1;b<<=1; 17 for(int i=0;i<d;++i)for(int j=0;j<b;++j)t[i==d-1?0:i+1][j^n]=dp[i][j]; 18 for(int i=0;i<d;++i)for(int j=0;j<b;++j)add(dp[i][j],t[i][j]); 19 } 20 int main(){//freopen("stone8.in","r",stdin); 21 n=read(); d=read(); 22 for(int i=0;i<n;++i)c[read()]++; 23 for(int i=fac[0]=1;i<S;++i)fac[i]=fac[i-1]*1ll*i%mod; 24 inv[S-1]=qp(fac[S-1],mod-2); 25 for(int i=S-2;~i;--i)inv[i]=inv[i+1]*(i+1ll)%mod; 26 for(int i=0;i<S;++i)if(c[i]&1)v^=i; 27 dp[0][v]=1; 28 for(int i=S-1;i;--i)while(c[i])ctb(i),c[i]--; 29 cout<<(dp[0][0]-(n%d==0))<<endl; 30 }
T2:路径计数
大意:有向图,多次询问$s,t,d$表示从$s$开始在$t$结束经过$d$条边且中途不经过$s,t$的方案数。$n \le 100,m \le n(n-1),d \le 50,q \le 10^6$
暴力就是设$dp[i][j][k][now]$表示从$i$出发到$j$目前走了$k$步停在$now$上的方案数。$O(n^4d)$
只有两个特殊点,看起来就像容斥。
设$f[i][j][k]$表示$i$走到$j$用了$k$步且中途不经过$i,j$的方案数。再设$g[i][j][k]$表示从$i$到$j$可以经过任何点走了$k$步的方案数。
$g$的转移比较简单。
$f$的非法状态我们把它分类以不重不漏:我们按照每条非法路径经历的第一个非法点分类,那无非就是$i,j$两种。再枚举$d$表示到非法点为止走了几步。
如果非法点是$j$,那么就是说用了$d$步从$i$走到$j$不经过$i,j$,然后从$j$任意走到$j$。这就分别是$f[i][j][d]$和$g[j][j][k-d]$
如果非法点是$i$,那么就是说用了$d$步从$i$走到$i$不经过$i,j$,然后从$i$任意走到$j$。后者就是$g[i][j][k-d]$。前者不知道。
于是我们再设一个$h[i][j][k]$表示不经过$i,j$从$i$走到$i$用了$k$步的方案数。
这样我们就知道$f[i][j][k]=g[i][j][k] - \sum\limits_{d=1}^{k-1} f[i][j][d] \times g[j][j][k-d] + \sum\limits_{d=1}^{k-1} h[i][j][d] \times g[i][j][k-d]$
要注意$i=j$时需要特判。非法点只有一个。
考虑怎么求得$h[i][j][k]$。类似上面的思路,我们去分别考虑第一个非法点是什么。
和上面一样讨论一下就能得到$h[i][j][k]=g[i][i][k] - \sum\limits_{d=1}^{k-1} h[i][j][d] \times g[i][i][k-d] + \sum\limits_{d=1}^{k-1} f[i][j][d] \times g[j][i][k-d]$
同样要注意$i=j$的特判。这样,三个东西的状态数都是$O(n^2d)$的,$g$的转移数是$O(n)$的,$f,h$的转移数是$O(d)$的。
所以总的复杂度是$O(n^3d+n^2d^2)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 int read(){ 4 register int p=0;register char ch=getchar(); 5 while(!isdigit(ch))ch=getchar(); 6 while(isdigit(ch))p=(p<<3)+(p<<1)+ch-48,ch=getchar(); 7 return p; 8 } 9 int f[111][111][55],g[111][111][55],h[111][111][55],n,m,mod; 10 //f:i->j (ban i j) g:i->j h:i->i(ban i j) 11 void add(int&a,int b){a+=b;if(a>=mod)a-=mod;if(a<0)a+=mod;} 12 vector<int>v[111]; 13 int main(){ 14 n=read(),m=read(),mod=read(); 15 for(int i=0,a,b;i<m;++i)a=read(),b=read(),v[a].push_back(b); 16 for(int i=1;i<=n;++i)f[i][i][0]=g[i][i][0]=h[i][i][0]=1; 17 for(int d=1;d<55;++d){ 18 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k:v[j]) 19 add(g[i][k][d],g[i][j][d-1]); 20 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) 21 f[i][j][d]=g[i][j][d],h[i][j][d]=g[i][i][d]; 22 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int D=1;D<d;++D){ 23 f[i][j][d]=(f[i][j][d]-1ll*f[i][j][D]*g[j][j][d-D]%mod+mod)%mod; 24 if(i!=j)f[i][j][d]=(f[i][j][d]-1ll*h[i][j][D]*g[i][j][d-D]%mod+mod)%mod; 25 } 26 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int D=1;D<d;++D){ 27 h[i][j][d]=(h[i][j][d]-1ll*h[i][j][D]*g[i][i][d-D]%mod+mod)%mod; 28 if(i!=j)h[i][j][d]=(h[i][j][d]-1ll*f[i][j][D]*g[j][i][d-D]%mod+mod)%mod; 29 } 30 //for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)printf("f[%d][%d][%d]=%d\ng[%d][%d][%d]=%d\nh[%d][%d][%d]=%d\n",i,j,d,f[i][j][d],i,j,d,g[i][j][d],i,j,d,h[i][j][d]); 31 }for(int q=read(),s,t,d;q--;)s=read(),t=read(),d=read(),printf("%d\n",f[s][t][d]); 32 }
T3:方格操作
原题面:
抽象题意:矩阵,初始给出$q$矩形,被包含次数为奇数的点为白色其余为黑色。每一轮开始前,代价增加(总白点数量)。
然后把所有点弄成黑色后,对于每行每列若上一轮中有奇数个白点就翻转整行列的状态。不断执行每一轮直到所有点全变为黑色。求总代价(无穷则$-1$)
$n,m,q \le 10^5$
把题意抽象成上面我说的这个模样,问题就很简单了。
(我把白点所在位置要翻转$1$次,变成了自己翻转一次,行一次,列一次一共$3$次,显然对答案没影响,但是有了自己翻转一次就有抽象题意中的“所有点弄成黑色”就好做些了)
首先第一轮的代价可以直接线段树+扫描线解决。
然后我们给每一行列都打上标记表示下一轮是否会被翻转。
可以发现矩阵两行相交换没有影响,列同理。于是只需要记录有多少行列会被翻转即可。
然后就可以模拟了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 111111 4 int v[S<<2],lz[S<<2],n,m,q,row[S],col[S],c1,d1;long long ans; 5 #define lc p<<1 6 #define rc lc|1 7 #define md (L+R>>1) 8 vector<int>ll[S],rr[S]; map<int,int>M[S]; 9 void modify(int l,int r,int p=1,int L=1,int R=m){ 10 if(l<=L&&R<=r){v[p]=R-L+1-v[p];lz[p]^=1;return;} 11 if(lz[p])v[lc]=md-L+1-v[lc],v[rc]=R-md-v[rc],lz[lc]^=1,lz[rc]^=1,lz[p]=0; 12 if(l<=md)modify(l,r,lc,L,md); if(r>md)modify(l,r,rc,md+1,R); v[p]=v[lc]+v[rc]; 13 } 14 int main(){ 15 cin>>n>>m>>q; 16 for(int i=1,x,y,X,Y;i<=q;++i){ 17 scanf("%d%d%d%d",&x,&y,&X,&Y); 18 X++;Y++; 19 row[x]^=(Y^y)&1;row[X]^=(Y^y)&1; 20 col[y]^=(X^x)&1;col[Y]^=(X^x)&1; 21 ll[x].push_back(y);ll[X].push_back(y); 22 rr[x].push_back(Y-1);rr[X].push_back(Y-1); 23 } 24 for(int i=1;i<=n;++i){ 25 for(int j=0;j<ll[i].size();++j)modify(ll[i][j],rr[i][j]); 26 ans+=v[1];cerr<<ans<<endl; 27 } 28 for(int i=1;i<=n;++i)row[i]^=row[i-1],c1+=row[i]; 29 for(int i=1;i<=m;++i)col[i]^=col[i-1],d1+=col[i]; 30 while(1){ 31 if(M[c1][d1])return puts("-1"),0; M[c1][d1]=1; 32 if(1ll*c1*(m-d1)+1ll*d1*(n-c1)==0)return cout<<ans,0; 33 ans+=1ll*c1*(m-d1)+1ll*d1*(n-c1); 34 int C1=c1,D1=d1; 35 c1=C1*((m-D1)&1)+(n-C1)*(D1&1); 36 d1=D1*((n-C1)&1)+(m-D1)*(C1&1); 37 } 38 }