[考试反思]0131省选模拟14:遗失

100+0+40=140.rk3

凑合?

然而T2调试语句没删丢了20。(代码开头读入之前一个赫然的return 0)

T2大概转化了题意,然后是杨氏矩阵的裸体了。然而我不会杨氏矩阵。

尽力了。能打的都打了。还不错吧。。。

中午就改完了T2,然后对着T3看了差不多一下午。。。

感觉题解有锅啊。。。并没有人会解释。。。于是弃掉了

 

T1:开车

大意:图,第i条边权值为$2^i$。求每条边都经过一次的最短回路。$n \le 100000, m \le 500000$

先强制每条边都走一次,于是每个点度数已知。只要让所有点度数都为偶数就形成了回路。

由于边权的特殊性,前$i-1$条边加起来也没有第i条边大,所以一定是用最小生成树上的边来凑度数。

跑最小生成树然后做个类似贪心的东西就没了。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 1000000007
 4 #define S 888888
 5 int fir[S],l[S],to[S],v[S],d[S],n,m,f[S],V=1,ans,ec;
 6 void add(int&a,int b){a+=b;if(a>=mod)a-=mod;}
 7 int find(int p){return f[p]==p?p:f[p]=find(f[p]);}
 8 void link(int a,int b,int w){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=w;}
 9 int dfs(int p,int fa,int x=0){
10     for(int i=fir[p];i;i=l[i])if(to[i]!=fa)x=dfs(to[i],p),add(ans,x*v[i]),d[p]+=x;
11     return d[p]&1;
12 }
13 int main(){
14     scanf("%d%d",&n,&m);
15     for(int i=1;i<=n;++i)f[i]=i;
16     for(int i=1,a,b;i<=m;++i){
17         add(V,V);add(ans,V);
18         scanf("%d%d",&a,&b);d[a]++;d[b]++;
19         if(find(a)!=find(b))f[f[a]]=f[b],link(a,b,V),link(b,a,V);
20     }dfs(1,0);printf("%d\n",ans);
21 }
View Code

 

T2:上分

大意:若要解锁(i,j)必须解锁(i,j-1),(i-1,j)。(x,y)在x<y时无需解锁。给定两点(a,b),(c,d)。求解锁这两个点在保证总解锁点数最小的情况下解锁顺序方案数。

$b \le a,d \le c, \ a,c\le 1000000$保证最小总解锁点数小于等于1000000。

斜着的坐标系看着难受,转过来。把(i,j)变成(i-j+1,j)。这样限制就变成了必须先解锁左边和上边的元素。

然后就是杨氏矩阵裸题了。

杨氏矩阵是指一类:如果这个点有元素,那么右方和下方要么有元素,要么比这个点大。这样的矩阵就是杨氏矩阵。

引入钩子引理:$P=\frac{n!}{\prod h_{i,j}}$。P是指在确定杨氏矩阵的形状的情况下,在每个有元素的位置上填$1$到$n$使之满足杨氏矩阵性质的方案数。

$h_{i,j}$表示$(i,j)$这个点的钩子长:右方和下方(不含)的元素个数和+1。

这道题杨氏矩阵的形状就是一个矩形,或两个相交的左上角重叠的矩形。

预处理阶乘的前缀积,用那种二位前缀和一样的思路差分一下即可。

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 #define mod 1000000007
 5 int fac[1234567],a,b,c,d,ffac[1234567],iinv[1234567];
 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 cal(int a,int b){return a*b?1ll*ffac[a+b-1]*iinv[a-1]%mod*iinv[b-1]%mod:1;}
 8 int ical(int a,int b){return a*b?1ll*iinv[a+b-1]*ffac[a-1]%mod*ffac[b-1]%mod:1;}
 9 int cal(int a,int b,int c,int d){return 1ll*ical(a,b-d)*ical(d,c-a)%mod*ical(b,c)%mod*ical(b-d,c-a)%mod*cal(c-a,b)%mod*cal(c,b-d)%mod;}
10 int main(){fac[0]=ffac[0]=iinv[0]=1;//freopen("1.in","r",stdin);
11     for(int i=1;i<=1000000;++i)fac[i]=fac[i-1]*1ll*i%mod,ffac[i]=ffac[i-1]*1ll*fac[i]%mod,iinv[i]=qp(ffac[i],mod-2);
12     int t,A,B,C,D;scanf("%d",&t);
13     while(t-->0){
14         scanf("%d%d%d%d",&A,&B,&C,&D);
15         A-=B-1;C-=D-1;
16         if(A>C)swap(A,C),swap(B,D);
17         if(B<=D)printf("%lld\n",1ll*fac[C*D]*ical(C,D)%mod);
18         else printf("%lld\n",1ll*fac[A*B+C*D-A*D]*cal(A,B,C,D)%mod);
19     }
20 }
View Code

 

T3:隔膜

大意:博弈,轮流操作n个有价值硬币,最开始第奇数个正面朝上其他反面。第$i$次操作可以在第$i,i+1$两个硬币中选一个翻面或者啥也不干。奇数次操作由先手方执行。

先手方得分是最后所有正面朝上的硬币价值和。求$n-1$次操作后最优决策下先手方得分。$m$次修改,每次修改会降低一个硬币的权值,保证时刻为正。每次修改后求解。

$n,m \le 200000$

暴力是一个比较经典的博弈论的倒着dp的思路。设dp[i][0/1]表示第i次操作前硬币i正反面朝上时,两人后续采取最优决策时先手方的得分。         

暴力就可以写了。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,a[222222];long long dp[2][222222];
 4 int main(){
 5     scanf("%d",&n);
 6     for(int i=1;i<=n;++i)scanf("%d",&a[i]);
 7     dp[1][n]=a[n];
 8     for(int i=n-1;i;--i)
 9         if(i&1)dp[1][i]=a[i]+dp[1][i+1],dp[0][i]=max(a[i]+dp[0][i+1],dp[1][i+1]);
10         else dp[0][i]=dp[0][i+1],dp[1][i]=min(dp[1][i+1],dp[0][i+1]+a[i]);
11     printf("%lld\n",dp[1][1]);
12     scanf("%d",&m);
13     while(m-->0){int p,d;
14         scanf("%d%d",&p,&d);a[p]-=d;
15         dp[1][n]=a[n];
16         for(int i=min(n-1,p);i;--i)
17             if(i&1)dp[1][i]=a[i]+dp[1][i+1],dp[0][i]=max(a[i]+dp[0][i+1],dp[1][i+1]);
18             else dp[0][i]=dp[0][i+1],dp[1][i]=min(dp[1][i+1],dp[0][i+1]+a[i]);
19         printf("%lld\n",dp[1][1]);
20     }
21 }
View Code

正解的话说的很有道理,但是不会证明。

说是在的代码不会很难写,应该说很简单。但是不会证写着就很没意思,所以先鸽了。

 

posted @ 2020-01-31 21:50  DeepinC  阅读(171)  评论(0编辑  收藏  举报