省选欢乐赛 题解
昨天沈老师神仙场整不会了。然后今天经典老题。
不是很懂为什么三道题题目名称都是 Delov。
卷王
发现如果答案为第 \(t\) 秒,那么这个序列一定是一个 \(1\)、两个连续的 \(1\)、三个连续的 \(1\)……一直到 \(t\) 个连续的 \(1\)(中间可能有没有的项,即不操作)异或起来。那随便跑个状压就行了。他 \(n\le 16\),跑起来松松松。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
int n;
bool dp[20][20][1<<16];
char s[20];
void solve(int n){
dp[n][0][0]=1;
for(int i=1;i<=n;i++){
int ret=0;
for(int j=1;j<=i;j++)ret=ret<<1|1;
for(int j=0;j<(1<<n);j++){
dp[n][i][j]|=dp[n][i-1][j];
for(int k=1;k<=n;k++){
int s=(ret<<k-1)&((1<<n)-1);
dp[n][i][j^s]|=dp[n][i-1][j];
}
}
}
}
int main(){
for(int i=1;i<=16;i++)solve(i);
int tim;scanf("%d",&tim);
while(tim--){
scanf("%s",s+1);n=strlen(s+1);int t=0;
for(int i=n;i>=1;i--){
t=(t<<1)|(s[i]=='1');
}
for(int i=0;i<=n;i++)if(dp[n][i][t]){
printf("%d\n",i);break;
}
}
return 0;
}
赢王
首先这个操作可以变成前缀和单点 \(\pm 1\)。然后有一个显然的 \(O(n^2)\) 暴力,场上 11:40 开始写写了三分钟写出来了。
然后显然只有相邻的有贡献,贡献是一个显然的式子。
然后不会了,全篇对着 yspm 贺。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int mod=998244353;
int n,k,ans,a[1000010],sum[1000010],lsh[1000010],cnt[1000010],cnt2[1000010];
struct BIT{
int sum[1000010],cnt[1000010];
#define lowbit(x) (x&-x)
void update(int x,int num){
int val=1ll*num*lsh[x]%mod;
while(x<=n)cnt[x]+=num,sum[x]+=val,x+=lowbit(x);
}
int querycnt(int x){
int sum=0;while(x)sum=(sum+cnt[x])%mod,x-=lowbit(x);return sum;
}
int querysum(int x){
int ans=0;while(x)ans=(ans+sum[x])%mod,x-=lowbit(x);return ans;
}
}c;
int query(int l,int r,int val,int od){
return (c.querycnt(r)-c.querycnt(l-1))*val+(c.querysum(r)-c.querysum(l-1))*od;
}
signed main(){
scanf("%lld%lld",&n,&k);lsh[0]++;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);sum[i]=(sum[i-1]+a[i])%k;
lsh[++lsh[0]]=sum[i];
}
sort(lsh+1,lsh+lsh[0]+1);
lsh[0]=unique(lsh+1,lsh+lsh[0]+1)-lsh-1;
ans-=1ll*n*(n+1)/2;
for(int i=0;i<=n;i++){
sum[i]=lower_bound(lsh+1,lsh+lsh[0]+1,sum[i])-lsh;
ans+=cnt[sum[i]];cnt[sum[i]]++;
}
cnt[sum[0]]--;cnt2[sum[0]]++;
c.update(sum[0],cnt[sum[0]]);
for(int i=1;i<=n;i++){
ans+=query(lower_bound(lsh+1,lsh+lsh[0]+1,lsh[sum[i]]-k/2)-lsh,sum[i],lsh[sum[i]],-1);
ans+=query(lower_bound(lsh+1,lsh+lsh[0]+1,lsh[sum[i]]+(k+1)/2)-lsh,lsh[0],lsh[sum[i]]+k,-1);
ans+=query(1,upper_bound(lsh+1,lsh+lsh[0]+1,lsh[sum[i]]-k/2-1)-lsh-1,k-lsh[sum[i]],1);
ans+=query(sum[i]+1,upper_bound(lsh+1,lsh+lsh[0]+1,lsh[sum[i]]+(k+1)/2-1)-lsh-1,-lsh[sum[i]],1);
cnt[sum[i]]--;cnt2[sum[i]]++;
c.update(sum[i],cnt[sum[i]]-cnt2[sum[i]]+1);
}
ans=(ans+mod)%mod;
printf("%lld\n",ans);
return 0;
}
稳王
论从 8:20 干到 11:30 拿了 63 分这件事。使用了一页半的算草。挺蚌埠的。
首先一定是攒一波牌然后一轮打掉。期望不会,考虑 PGF 的一般套路,答案就是 \(G(1)\),\(G\) 是第 \(i\) 轮没结束的概率。直接推 \(G\) 看上去比较可做,把 \(g_0=1\) 去掉,把每个情况拆开:
- 全毒:\(\dfrac{1-\left(\frac 13\right)^n}2\)。
- 全火:\(\dfrac{1-\left(\frac 13\right)^{\lfloor\frac{n-1}2\rfloor}}2\)。
- 全复:\(\frac 12\)。
- 火复:\(2\left(1-\left(\dfrac 23\right)^{\lfloor\frac{n-1}2\rfloor}\right)\) 再减掉 \(2\times\) 全火。
- 毒火:这时候有个组合数的式子,但是看起来就很不可做的样子。
观察数据范围 \(10^{18}\),合理猜测是个快速幂。考虑矩阵递推。首先对牌递推不太好办,直接对血递推,然后把没打死的概率加起来。对于毒火,毒伤害是 \(1\)(把第一张毒也算进来),火伤害是 \(3\)。强制都选,可以容斥一下选了几个,也可以暴力 dp 加维:设 \(dp_{i,0/1,0/1}\) 为 \(i\) 点伤害有无毒和火,转移显然。然后就可以矩阵加速递推了。
这是场上的 \(O(n)\) 暴力递推代码。为什么不冲矩阵一会说。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int mod=998244353,inv2=(mod+1)>>1,inv3=(mod+1)/3;
long long n;
int ans;
int qpow(int a,long long b){
int ans=1;
while(b){
if(b&1)ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
int dp[100010][2][2],f[100010][2][2][2];
signed main(){
int tim;scanf("%lld",&tim);
while(tim--){
scanf("%lld",&n);ans=0;
ans=(ans+inv2+1)%mod;
ans=(ans+1ll*(1-qpow(inv3,n)+mod)%mod*inv2)%mod;
ans=(ans-1ll*(1-qpow(inv3,n-1>>1)+mod)%mod*inv2%mod+mod)%mod;
ans=(ans+2ll*(1-qpow(2ll*inv3%mod,n-1>>1)+mod))%mod;
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
if(i>=1)dp[i][1][0]=1ll*(dp[i-1][1][0]+dp[i-1][0][0])%mod*inv3%mod;
if(i>=3)dp[i][0][1]=1ll*(dp[i-3][0][1]+dp[i-3][0][0])%mod*inv3%mod;
if(i>=3)dp[i][1][1]=1ll*(1ll*dp[i-1][0][1]+dp[i-1][1][1]+dp[i-3][1][0]+dp[i-3][1][1])%mod*inv3%mod;
ans=(ans+dp[i][1][1])%mod;
}
for(int i=1;i<=n;i++)dp[i][1][0]=dp[i][0][1]=dp[i][1][1]=0;
for(int i=1;i<=n;i++){
if(i>=1)dp[i][1][0]=1ll*(dp[i-1][1][0]+dp[i-1][0][0])%mod*inv3%mod;
if(i>=2)dp[i][0][1]=1ll*(dp[i-2][0][1]+dp[i-2][0][0])%mod*inv3%mod;
if(i>=2)dp[i][1][1]=1ll*(1ll*dp[i-1][0][1]+dp[i-1][1][1]+dp[i-2][1][0]+dp[i-2][1][1])%mod*inv3%mod;
ans=(ans+dp[i][1][1])%mod;
}
for(int i=1;i<=n;i++)dp[i][1][0]=dp[i][0][1]=dp[i][1][1]=0;
f[0][0][0][0]=1;
for(int i=1;i<=n;i++){
if(i>=1)f[i][1][0][0]=1ll*(f[i-1][1][0][0]+f[i-1][0][0][0])%mod*inv3%mod;
if(i>=3)f[i][0][1][0]=1ll*(f[i-3][0][1][0]+f[i-3][0][0][0])%mod*inv3%mod;
if(i>=3)f[i][1][1][0]=1ll*(1ll*f[i-1][0][1][0]+f[i-1][1][1][0]+f[i-3][1][0][0]+f[i-3][1][1][0])%mod*inv3%mod;
if(i>=4)f[i][0][0][1]=1ll*(f[i-4][0][0][1]+f[i-4][0][0][0])%mod*inv3%mod;
if(i>=4)f[i][1][0][1]=1ll*(1ll*f[i-1][0][0][1]+f[i-1][1][0][1]+f[i-4][1][0][0]+f[i-4][1][0][1])%mod*inv3%mod;
if(i>=4)f[i][0][1][1]=1ll*(1ll*f[i-3][0][0][1]+f[i-3][0][1][1]+f[i-4][0][1][0]+f[i-4][0][1][1])%mod*inv3%mod;
if(i>=4)f[i][1][1][1]=1ll*(1ll*f[i-1][0][1][1]+f[i-1][1][1][1]+f[i-3][1][0][1]+f[i-3][1][1][1]+f[i-4][1][1][0]+f[i-4][1][1][1])%mod*inv3%mod;
ans=(ans+f[i][1][1][1])%mod;
}
for(int i=1;i<=n;i++)f[i][1][0][0]=f[i][0][1][0]=f[i][1][1][0]=f[i][0][0][1]=f[i][1][0][1]=f[i][0][1][1]=f[i][1][1][1]=0;
printf("%lld\n",ans);
}
return 0;
}
如果你容斥的话那矩阵会很小,可以手推出来。但是如果你暴力 dp 加维还像我这样比较不带脑子的写的话,那么最后一个矩阵是 \(25\times 25\) 的。然后我当时看到就直接润了。
蚌埠了,贺了一份 Delov 的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int mod=998244353,inv2=(mod+1)>>1,inv3=(mod+1)/3;
long long n;
int ans;
int qpow(int a,long long b){
int ans=1;
while(b){
if(b&1)ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
struct node{
int n,a[6][6];
node(){memset(a,0,sizeof(a));n=0;}
void clear(){memset(a,0,sizeof(a));n=0;}
node operator*(const node&s)const{
node ret;ret.n=n;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++){
ret.a[i][j]=(ret.a[i][j]+1ll*a[i][k]*s.a[k][j])%mod;
}
return ret;
}
};
node qpow(node a,long long b){
node ans;ans.n=a.n;
for(int i=1;i<=ans.n;i++)ans.a[i][i]=1;
while(b){
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
int calc(int x,int n){
return 1ll*x*(1-qpow(x,n)+mod)%mod*qpow(1-x+mod,mod-2)%mod;
}
signed main(){
int tim;scanf("%lld",&tim);
while(tim--){
scanf("%lld",&n);ans=0;
ans=(ans+inv2+1)%mod;
ans=(ans+1ll*(1-qpow(inv3,n)+mod)%mod*inv2)%mod;
ans=(ans-1ll*(1-qpow(inv3,n-1>>1)+mod)%mod*inv2%mod+mod)%mod;
ans=(ans+2ll*(1-qpow(2ll*inv3%mod,n-1>>1)+mod))%mod;
node a,b;a.n=b.n=3;
a.a[1][1]=1;
b.a[1][1]=b.a[2][1]=b.a[1][3]=b.a[2][3]=inv3;
b.a[1][2]=b.a[3][3]=1;
a=a*qpow(b,n);
ans=(ans+a.a[1][3])%mod;
ans=(ans-calc(inv3,n>>1)+mod)%mod;
ans=(ans-calc(inv3,n)+mod)%mod;
a.clear();b.clear();a.n=b.n=4;
a.a[1][1]=1;
b.a[1][1]=b.a[1][4]=b.a[3][1]=b.a[3][4]=inv3;
b.a[1][2]=b.a[2][3]=b.a[4][4]=1;
a=a*qpow(b,n);
ans=(ans+a.a[1][4])%mod;
ans=(ans-calc(inv3,n)+mod)%mod;
ans=(ans-calc(inv3,n/3)+mod)%mod;
a.clear();b.clear();a.n=b.n=5;
a.a[1][1]=1;
b.a[1][1]=b.a[1][5]=b.a[3][1]=b.a[4][1]=b.a[3][5]=b.a[4][5]=inv3;
b.a[1][2]=b.a[2][3]=b.a[3][4]=b.a[5][5]=1;
a=a*qpow(b,n);ans=(ans+a.a[1][5])%mod;
a.clear();b.clear();a.n=b.n=5;
a.a[1][1]=1;
b.a[1][1]=b.a[1][5]=b.a[3][1]=b.a[3][5]=inv3;
b.a[1][2]=b.a[2][3]=b.a[3][4]=b.a[5][5]=1;
a=a*qpow(b,n);ans=(ans-a.a[1][5]+mod)%mod;
a.clear();b.clear();a.n=b.n=5;
a.a[1][1]=1;
b.a[1][1]=b.a[1][5]=b.a[4][1]=b.a[4][5]=inv3;
b.a[1][2]=b.a[2][3]=b.a[3][4]=b.a[5][5]=1;
a=a*qpow(b,n);ans=(ans-a.a[1][5]+mod)%mod;
a.clear();b.clear();a.n=b.n=5;
a.a[1][1]=1;
b.a[3][1]=b.a[4][1]=b.a[3][5]=b.a[4][5]=inv3;
b.a[1][2]=b.a[2][3]=b.a[3][4]=b.a[5][5]=1;
a=a*qpow(b,n);ans=(ans-a.a[1][5]+mod)%mod;
ans=(ans+calc(inv3,n))%mod;
ans=(ans+calc(inv3,n/3))%mod;
ans=(ans+calc(inv3,n/4))%mod;
printf("%lld\n",ans);
}
return 0;
}