[考试反思]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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

posted @ 2020-03-28 16:36  DeepinC  阅读(206)  评论(2编辑  收藏  举报