CCF NOI 2018温馨提示:
做题千万条,读题第一条。
多测不清空,爆零两行泪。
将CCF的告诫敬给愚蠢的自己。T1多测清空没清干净AC代码爆零了。
两个打IOI赛制的得分是170/140。(fixed,kx大神怎么可能只有135分呢?)如果我清空了勉强可以和skyh持平。
但是没有如果。结果就是「菜」
千万长记性。
还是习惯性的利用了「没有赋值的数组初值为0」的特性,但是清空不彻底就导致崩盘了。
上来三道题,T1是noip模拟测试3的原题,没看出来,也不会做。但是换题了。
然后换上了一个弱化改编版的《无限之环》,想都没想就开始写(我颓题解颓的那么认真),过样例就交,然后我就以为我AC了。
手模了好几个一组测试数据的样例,全过了,很kx。
因为文件的问题重交了一遍,其实是40分钟就写完了。
然后看当时跳过了《无限之环》的miku在我旁边苦写插头dp,感觉他好可怜。
然而其实可怜的是我好歹人家还有40分。。。
然后就结束了。T2一点想法没有,T3写了一个全排列。
然后既然T1已经觉得自己AC了,T2啥也不会,那么也只能想T3的部分分了啊。
看到$a_i$只有2种的那个点,于是就发现状态有很多冗余,有些长得完全一样的全排列就不需要枚举多次了啊。
然后继续优化这个想法,反正剩下的时间也没事干,博一博信仰写个记忆化搜索搜出多少是多少啊
期望得分10分的我没什么想法,看着应该AC了的T1和等同于空着的T2,不知道何去何从。
于是我回到LCT2专题把LCA那道题AC了(差分维护前缀和的区间操作区间查询树状数组真是帅啊!)
调了那么就原来我的树状数组没有锅,题目点编号从0开始,代码里有一处忘+1了无良出题人
然后考试就结束了,惊奇的发现自己T1TLE0了。
我是傻子2333
T1:铁轨建设
题目大意:网格图。用若干回路完全覆盖所有非障碍格,有些关键点,在关键点上路径为直线时产生1代价。问最小代价。多组测试数据。n,m<=25
简化改编的《无限之环》。只有L型与直线型。
我们先把所有点拆成4个表示上下左右,然后把图黑白染色,黑连源白连汇。
每个连源的格点有2流量,连汇的也是。如果图满流了那么就是形成了回路。
考虑怎么翻转/掰直铁轨。我们先假定所有铁轨都是向右与向上的,现在夹角是90度。
夹角是90度时是弯铁轨,代价为0。夹角为180度时是直铁轨,代价为0。
我们发现,如果把铁轨的一支翻转180度,翻转后夹角依旧是90度,是弯铁轨,不贡献代价。所以右向左,上向下连边,流量1费用0。
如果翻转铁轨的一支90度,那么翻转之后夹角是180度,就是直铁轨了,有1代价。所以右向下连边,上向左连边,流量1费用1。
然后就是一个最小费用最大流了。
1 #include2 int min(int a,int b){return aa:b;} 3 int A[28][28],n,m,t,o[28][28][4],pc,ec=1,S,E,cost,maxflow,out,in,al[2555]; 4 int fir[2555],l[666666],to[666666],w[666666],c[666666],q[6666666],iq[2555],dt[2555]; 5 void link(int a,int b,int W,int C){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;w[ec]=W;c[ec]=C;} 6 void con(int a,int b,int W,int C){if(a==0||b==0)return;link(a,b,W,C);link(b,a,0,-C);} 7 bool bfs(){ 8 for(int i=1;i<=pc;++i)dt[i]=1234567890,al[i]=0;q[1]=S;dt[S]=0; 9 for(int h=1,t=1;h<=t;iq[q[h]]=0,++h)for(int i=fir[q[h]];i;i=l[i])if(w[i]&&dt[to[i]]>dt[q[h]]+c[i]){ 10 dt[to[i]]=dt[q[h]]+c[i]; 11 if(!iq[to[i]])iq[q[++t]=to[i]]=1; 12 }return dt[E]!=1234567890; 13 } 14 int dfs(int p,int flow){int r=flow; 15 if(p==E)return r;al[p]=1; 16 for(int i=fir[p];i&&r;i=l[i])if(dt[to[i]]==dt[p]+c[i]&&!al[to[i]]&&w[i]){ 17 int x=dfs(to[i],min(r,w[i])); 18 if(!x)dt[to[i]]=1234567890; 19 w[i]-=x;w[i^1]+=x;r-=x;cost+=x*c[i]; 20 }al[p]=0;return flow-r; 21 } 22 main(){freopen("railway.in","r",stdin);freopen("railway.out","w",stdout); 23 scanf("%d",&t); 24 while(t--){ 25 scanf("%d%d",&n,&m); 26 #define O(x) o[i][j][x] 27 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&A[i][j]); 28 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(A[i][j])for(int k=0;k<4;++k)O(k)=++pc; 29 S=++pc;E=++pc; 30 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(A[i][j]) 31 if(i+j&1)con(S,O(0),1,0),con(S,O(1),1,0),con(O(0),O(2),1,0),con(O(1),O(3),1,0),con(O(0),O(3),1,A[i][j]-1),con(O(1),O(2),1,A[i][j]-1); 32 else con(O(0),E,1,0),con(O(1),E,1,0),con(O(2),O(0),1,0),con(O(3),O(1),1,0),con(O(3),O(0),1,A[i][j]-1),con(O(2),O(1),1,A[i][j]-1); 33 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(i+j&1)if(A[i][j]) 34 con(O(0),o[i-1][j][2],1,0),con(O(1),o[i][j+1][3],1,0),con(O(2),o[i+1][j][0],1,0),con(O(3),o[i][j-1][1],1,0); 35 while(bfs())maxflow+=dfs(S,66666); 36 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(A[i][j])if(i+j&1)out+=2;else in+=2; 37 if(in!=out||in!=maxflow)puts("-1");else printf("%d\n",cost); 38 for(int i=1;i<=pc;++i)fir[i]=0;maxflow=out=in=cost=pc=0;ec=1; 39 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int k=0;k<4;++k)o[i][j][k]=0; 40 } 41 }
T2:圈地游戏
咕?部分分都不会写写什么题解?
T3:组合数学
题目大意:给定数列a,对于其所有排列b求前缀和得到s,求$\sum\limits_{b=permutation(a)} \frac{m!}{\prod\limits_{i=2}^{n} s_i}$。m为a之和。n<=100,m<=1000
这种题的式子不少时候还是有含义的,用含义猜测式子有时会比较方便。
可以看到,这个式子的形式比较像可重排列数(感谢%%%LNC纠正定义)。所以从排列的角度出发来思考。
这里分母上是前缀和,而且还缺了一项$s_1$让人挺烦的,先把它补回来。
考虑外层求和记号下$permutation(a)$的含义,就是从那么多个玩意里依次选出来一种。
把m个球进行排列,每种$b_i$个,我们已知每种球的第一个出现的位置,它们的先后顺序就是排列p。
还是需要粘题解。它实在太正确了无法解释。(其实就是懒得手抄一遍)
上面的概率是针对于m个球的任意排列。所以是用$m!$乘上每一个概率,就是合法的方案数了。
特别的,因为题目的式子里并没有$s_1$,所以我们并不限制最后一种球的位置最靠前的那一个,它的编号恰好是1。
这样含义总算是出来了,现在我们抛掉原来含枚举排列的难以优化的式子,只从含义出发,去发现新的复杂度正确的式子。
首先,优先考虑特殊元素——那个位置最靠前的不一定是编号为1的那种球。
我们枚举它到底是那一种,暂时把它的种类编号成为last,这种球里出现位置最早的一个所在的位置为pos。
根据定义,它后面不应该再有其它种类的1号球了,但是直接求解很难,我们来考虑容斥。
设f(x)是恰好有x种球的1号球在pos后面。那么我们需要求解的就是f(0)。因为我们已经说了它是最后一个。
直接求解不行,那么就求解「至少」。设g(x)是至少有x种球的1号球在pos后面。
接下来%%%LNC的容斥严谨证明。
这里x其实不应该是一个变量来表示几种,而应该是一个集合来表示具体哪几种。
因为对于不同的种类,虽然它们的大小(种类数)相同,但是它们的转移值不同,所以只说集合大小是不严谨的。
根据定义,有$g(x) = \sum\limits_{x \subseteq y} f(y)$
根据子集反演,$f(x)=\sum\limits_{x \subseteq y} g(y) \times (-1)^{|y|-|x|}$
可以发现,这里的容斥系数只与大小有关,所以直接用大小转移而不加区分是正确的,但是并不代表可以直接那么推导。
所以说现在容斥系数也已经有了,问题就只剩下g的求解。
现在你已经枚举了last了,剩余的未知的就是pos了。于是我们也枚举它。
考虑总方案数:
首先,我们考虑钦定,在所有的last中选出一个放在pos位置上。答案乘a[last]
然后我们考虑1号求放在pos后面的那些种类的球,设它们一共有t个,那么它们任意排列的方案数为(t+a[last]-1)!。
之所以要减一,是因为你已经确定了last中的哪一个放在了pos上。
而pos后面的位置一共有m-pos个,在这些位置里你要挑出t+a[last]-1个来安排这些球,这是个组合数。
然后剩下的球还有(m-t-a[last])个。这个数的阶乘就是任意排列的方案。
但是这里有两处「任意排列」。其实并不能任意,因为你要求1号球必须在最前面了,所以还要除以$\prod\limits_{i \neq last}a_i$
全都乘起来,这就是总方案数。
可以发现,里面只有一项与pos有关,于是把它提出来单独求和,对于每个last计算一遍预处理出来。
然后就只与t有关了。你要知道它有几种方案,由几种球恰好凑出t个球。
这个就是一个经典的背包问题了,其中注意last是要必选的。求出这样的总方案数,然后还要容斥?
不需要。因为我们发现容斥的系数正负只与元素个数有关了,而元素个数恰好就是背包的物件数。
所以只要在背包转移时的+=全部转化为-=,这样系数就自动乘上了-1。(每多一个元素,容斥系数取相反数)
然后就没了。
1 #include2 using namespace std; 3 #define int long long 4 int fac[1005],m,n,inv[1005],a[1005],cnt,t[55],v[55],mx,dp[1005],C[1005][1005],INV=1,ans,f[10005]; 5 #define mod 1000000007 6 main(){ 7 scanf("%lld",&n);fac[0]=fac[1]=inv[1]=dp[0]=1; 8 for(int i=1;i<=n;++i)scanf("%lld",&a[i]),m+=a[i],INV=INV*a[i]%mod; 9 for(int i=2;i<=m;++i)fac[i]=fac[i-1]*i%mod,inv[i]=mod-mod/i*inv[mod%i]%mod; 10 for(int i=0;i<=m;++i)C[i][0]=1; 11 for(int i=1;i<=m;++i)for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; 12 for(int i=1;i<=m;++i)for(int j=i;j<=m;++j)f[i]=(f[i]+C[j-1][i-1])%mod; 13 for(int lst=1,tans;tans=a[lst],lst<=n;++lst){ 14 for(int i=1;i<=m;++i)dp[i]=0; 15 for(int i=1;i<=n;++i)if(i!=lst)tans=tans*inv[a[i]]%mod; 16 for(int i=1;i<=n;++i)if(i!=lst)for(int j=m;~j;--j)dp[j+a[i]]=(dp[j+a[i]]-dp[j]+mod)%mod; 17 for(int t=0;t<=m;++t)ans=(ans+dp[t]*fac[t+a[lst]-1]%mod*fac[m-t-a[lst]]%mod*f[t+a[lst]]%mod*tans)%mod; 18 }cout< endl; 19 }