【纪中模拟2018.11.02】【JZOJ5945】昆特牌
题目链接:https://jzoj.net/senior/#main/show/5945
Limits
TL: 1e3ms ML:512Mb
Description
原本昆特牌中有$k$种卡牌和$n$种阵营,且每个阵营拥有的卡牌种数都是相等的。由于故障,卡牌数据被打乱了,每个阵营现在有$a_{i}$种卡牌。因为昆特牌即将迎来重大更新,每种牌的所属阵营并不重要,工程师只想尽快让每个阵营拥有相同数量的卡牌。由于数据库的结构原因,你每单位时间只能将一种牌向左边或右边相邻的一个阵营移动。问:最终的卡牌分布有多少种方案。两种方案不同当且仅当存在一种卡牌,它在两种方案中所属阵营不同。对$998244353$取模。
Input
第一行一个整数$T$,表示数据组数。接下来每组数据,第一行一个整数$n$,第二行$n$个数,第$i$个数为$a_{i}$ ,意义见题目描述
Ouput
$T$行,每行一个数表示答案。
Sample Input 1
3
3
2 1 3
3
1 2 3
3
3 2 1
Sample Ouput 1
3
9
9
Sample Explanation 1
对于该组数据,初始为$\{\{1,2\}\{3\}\{4,5,6\}\}$,移动结束后为$\{\{1,2\}\{3,4\}\{5,6\}\}$$,$$\{\{1,2\}\{3,6\}\{4,5\}\}$$,$$\{\{1,2\}\{3,5\}\{4,6\}\}$
Sample Input 2
4
3
8 1 0
4
5 0 1 2
4
0 4 0 0
4
1 1 6 0
Sample Ouput 2
1120
30
24
270
Data Constraint
保证输入合法,$n\mid{}k,\sum_{i=1}^{n}a_i=k$
数据点 | $k\le$ | $n\le$ | $T\le$ |
1,2 | 10 | 5 | 10 |
3,4 | 1000 | 100 | 10 |
5,6 | 4000 | 10 | 10 |
7~10 | 1000000 | 1000 | 500 |
Solution
考场上,笔者分析题面,回忆起经典贪心例题均分纸牌,认为此题同样可以线性贪心转移求解。
读入牌堆数$n$、牌堆状态$a$。设最终各牌堆卡牌数为$ave$,则设差序列$b, \forall{}i\in{}[1,n],b_{i}=a_{i}-ave$
规定操作方向为下界至上界,每次操作$i$牌堆后清零$b_{i}$。
考虑$b_{i}>0$时,将$i$牌堆中所有超出$ave$的牌移至$i+1$牌堆,方案数为$\lgroup^{a_{i}}_{b_{i}}\rgroup$。
若$b_{i}<0$,则将$i+1$牌堆中取出$-b_{i}$张牌,移至$i$牌堆。此时若$i+1$牌堆足够多,方案数为$C_{a_{i+1}}^{-b_{i}}$;若$i+1$牌堆不够多,考虑实际移动牌的过程中,为了一遍转移,一定会先将$i+1$牌堆补成刚刚好足够补充$i$牌堆的状态,再将$i$牌堆补齐,同时$a_{i+1}$变为$ave$,方案数为$C_{ave-b_{i}}^{-b_{i}}$。实现过程中,将计算方案的$C$下标取$max$即可。
核心思想如上。观察数据范围,$k\le{}4e3$时,可以用杨辉三角预处理组合数,时间复杂度$O(k^2+T*n)$;$k\le{}1e6$时,预处理阶乘及其乘法逆元,时间复杂度$O(k+T*n)$。
Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<ctime> 6 using namespace std; 7 typedef long long LL; 8 bool isnum(char ch){ 9 if('0'<=ch&&ch<='9') 10 return 1; 11 return 0; 12 } 13 char ch; 14 bool sign; 15 void read(int& x){ 16 sign=0; 17 for(ch=getchar();!isnum(ch);ch=getchar()) 18 if(ch=='-') 19 sign=1; 20 x=ch-'0'; 21 for(ch=getchar();isnum(ch);ch=getchar()) 22 x=(x<<3)+(x<<1)+ch-'0'; 23 if(sign) 24 x=-x; 25 } 26 void read(LL& x){ 27 sign=0; 28 for(ch=getchar();!isnum(ch);ch=getchar()) 29 if(ch=='-') 30 sign=1; 31 x=ch-'0'; 32 for(ch=getchar();isnum(ch);ch=getchar()) 33 x=(x<<3)+(x<<1)+ch-'0'; 34 if(sign) 35 x=-x; 36 } 37 char stk[23]; 38 int cnt; 39 void write(LL x){ 40 if(x==0){ 41 putchar('0'); 42 return ; 43 } 44 if(x<0){ 45 putchar('-'); 46 x=-x; 47 } 48 cnt=0; 49 while(x){ 50 stk[++cnt]=x%10+'0'; 51 x/=10; 52 } 53 int i; 54 for(i=cnt;i>=1;i--) 55 putchar(stk[i]); 56 } 57 int T,n; 58 LL a[1003]; 59 LL b[1003]; 60 const LL mod=998244353; 61 LL jc[1000003]; 62 LL jcr[1000003]; 63 LL qpow(LL a,LL b){ 64 a%=mod; 65 LL ans=1; 66 while(b){ 67 if(b&1) 68 ans=(ans*a)%mod; 69 a=(a*a)%mod; 70 b>>=1; 71 } 72 return ans; 73 } 74 LL x,y,z; 75 LL C(LL n,LL m){ 76 x=jc[n]; 77 y=(jcr[m]*jcr[n-m])%mod; 78 z=(x*y)%mod; 79 return z; 80 } 81 LL ans; 82 LL max(LL x,LL y){ 83 if(x>y) 84 return x; 85 return y; 86 } 87 int main(){ 88 freopen("gwent.in","r",stdin); 89 freopen("gwent.out","w",stdout); 90 read(T); 91 int i; 92 jc[0]=jcr[0]=1; 93 for(i=1;i<=1000000;i++) 94 jc[i]=(jc[i-1]*i)%mod; 95 jcr[1000000]=qpow(jc[1000000],mod-2); 96 for(i=1000000;i>=2;i--) 97 jcr[i-1]=(jcr[i]*i)%mod; 98 LL sum,ave; 99 while(T--){ 100 read(n); 101 for(i=1;i<=n;i++) 102 read(a[i]); 103 sum=0; 104 for(i=1;i<=n;i++) 105 sum+=a[i]; 106 ave=sum/n; 107 for(i=1;i<=n;i++) 108 b[i]=a[i]-ave; 109 ans=1; 110 for(i=1;i<n;i++) 111 if(b[i]>0){ 112 ans=(ans*C(a[i],b[i]))%mod; 113 //leap i sends, ans*=C(a[i],b[i]) 114 a[i+1]+=b[i]; 115 b[i+1]+=b[i]; 116 a[i]-=b[i]; 117 b[i]=0;//set leap i empty 118 } 119 else if(b[i]<0){ 120 ans=(ans*C(max(ave-b[i],a[i+1]),-b[i]))%mod; 121 /* 122 leap i gets. 123 if(a[i+1]>ave) 124 straight from i+1, ans*=C(a[i],-b[i]) 125 else 126 borrow from farther, ans*=C(ave-b[i],-b[i]) 127 */ 128 a[i+1]+=b[i]; 129 b[i+1]+=b[i]; 130 a[i]-=b[i]; 131 b[i]=0;//set leap i empty 132 } 133 write(ans); 134 putchar('\n');//plural data, dont forget \n 135 } 136 return 0; 137 }
Conclusion
笔者能力不够,无法证明该贪心策略(极可能是错误的)。欢迎各位前来拍砖。