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 ",ans); 21 }
T2:上分
大意:若要解锁(i,j)必须解锁(i,j-1),(i-1,j)。(x,y)在x<y时无需解锁。给定两点(a,b),(c,d)。求解锁这两个点在保证总解锁点数最小的情况下解锁顺序方案数。
$b le a,d le c, a,cle 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 ",1ll*fac[C*D]*ical(C,D)%mod); 18 else printf("%lld ",1ll*fac[A*B+C*D-A*D]*cal(A,B,C,D)%mod); 19 } 20 }
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 ",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 ",dp[1][1]); 20 } 21 }
正解的话说的很有道理,但是不会证明。
说是在的代码不会很难写,应该说很简单。但是不会证写着就很没意思,所以先鸽了。