【考试总结】2022-03-11
卷王
考虑递推得到所有 的所有答案,将序列差分之后设答案为 在第 秒的操作形式就是将差分序列里面两个相隔 的位置反转
那么将 秒的所有操作反转过来进行 即可,复杂度
Code Display
const int N=20;
char s[N];
int n,ans[1<<16],a[N];
bool dp[17][1<<16];
signed main(){
freopen("roll.in","r",stdin); freopen("roll.out","w",stdout);
dp[0][0]=1;
int S=1<<16; --S;
for(int t=0;t<16;++t){
for(int i=0;i<=S;++i) if(dp[t][i]){
dp[t+1][i]=1;
for(int j=1;j<=16;++j){
int nS=i^(1<<(j-1));
if(j+t+1<=16) nS^=1<<(t+j);
dp[t+1][nS]=1;
}
}
}
for(int i=0;i<=S;++i){
for(int t=0;t<16;++t) if(dp[t][i]){ans[i]=t; break;}
}
int T=read(); while(T--){
scanf("%s",s+1); n=strlen(s+1);
reverse(s+1,s+n+1);
if(n<16) rep(i,n+1,16) s[i]='0';
n=16;
reverse(s+1,s+n+1);
rep(i,1,n) a[i]=s[i]-'0';
Down(i,n,1) a[i]^=a[i-1];
int init=0;
Down(i,n,1) init=init<<1|a[i];
print(ans[init]);
}
return 0;
}
赢王
设前缀和数组为
考虑在将 操作为 之后 在模 意义下值是一样的,那么操作前 个数字之后剩下给 的是
所以如果长度为 的序列 合法那么操作次数最小就是
对这个东西求和是经典问题,使用 来维护 每种余数的前缀和的个数和 的和
这里个数定义为能造成贡献的区间个数,对于一个前缀模 的余数 ,一个位置 带来的 的贡献数量是 前面 的 的数量 乘 后面 的 的数量
那么扫过一个 时带来的增量是可以计算的,这样子就可以树状数组维护了
在查询的时候每个 其它的 造成 的对答案的贡献形式是不一样的,需要分开讨论计算,平凡但是很难不言繁琐
Code Display
const int N=1e6+10;
int n,ans,a[N],k,sum[N];
int lsh[N],m=1,ton[N];
struct Fenwick_Tree{
int sum[N],cnt[N];
inline void insert(int pos,int num){
int val=mul(num,lsh[pos]);
for(int x=pos;x<=m;x+=x&(-x)) cnt[x]+=num,ckadd(sum[x],val);
return ;
}
inline int query_cnt(int x){
int res=0;
for(;x;x-=x&(-x)) ckadd(res,cnt[x]);
return res;
}
inline int query_sum(int x){
int res=0;
for(;x;x-=x&(-x)) ckadd(res,sum[x]);
return res;
}
}T;
int nowcnt[N];
inline int solve(int l,int r,int coef,int fl){
int cnt=T.query_cnt(r)-T.query_cnt(l-1);
int sum=T.query_sum(r)-T.query_sum(l-1);
return coef*cnt+sum*fl;
}
signed main(){
freopen("win.in","r",stdin); freopen("win.out","w",stdout);
n=read(); k=read();
rep(i,1,n) a[i]=read(),sum[i]=(sum[i-1]+a[i])%k,lsh[++m]=sum[i];
sort(lsh+1,lsh+m+1); m=unique(lsh+1,lsh+m+1)-lsh-1;
ans=-n*(n+1)/2;
rep(i,0,n) sum[i]=lower_bound(lsh+1,lsh+m+1,sum[i])-lsh,ans+=ton[sum[i]],ton[sum[i]]++;
T.insert(sum[0],--ton[sum[0]]); nowcnt[1]=1;
for(int i=1;i<=n;++i){
ans+=solve(lower_bound(lsh+1,lsh+m+1,lsh[sum[i]]-k/2)-lsh,sum[i],lsh[sum[i]],-1);
ans+=solve(lower_bound(lsh+1,lsh+m+1,lsh[sum[i]]+(k+1)/2)-lsh,m,lsh[sum[i]]+k,-1);
ans+=solve(1,upper_bound(lsh+1,lsh+m+1,lsh[sum[i]]-k/2-1)-lsh-1,k-lsh[sum[i]],1);
ans+=solve(sum[i]+1,upper_bound(lsh+1,lsh+m+1,lsh[sum[i]]+(k+1)/2-1)-lsh-1,-lsh[sum[i]],1);
ton[sum[i]]--; nowcnt[sum[i]]++;
int deltnum=ton[sum[i]]-nowcnt[sum[i]]+1;
T.insert(sum[i],deltnum);
}
ans=(ans%mod+mod)%mod;
print(ans);
return 0;
}
稳王
不难发现最优策略是把牌攒到能一次杀掉 再一起打出
问题本质上是计算每种不能杀死 的牌型的出现概率之和加 ,那么分为如下若干种情况讨论即可:
-
全拿到复读牌:
-
全部为毒药牌,不到 张不能获胜:
-
全部为火球牌,不拿到 不能获胜:
-
同时有毒药和火球牌,先出一张毒药,那么可以将答案表示为
但是这里并不能保证毒药和火球都被拿到了,所以要分别减掉 和 的前 项系数和
这里需要满足多出来一张毒药牌,所以可以将 再做
-
同时有毒药和复读牌,本质相同,这里的项变成了 ,容斥的方式也和上面完全一样
-
同时有 张牌,本质仍然相同,项变成了 ,算 次而已
注意单项没必要矩阵快速幂,可以直接等比数列求和
不需要预处理向量也是可以通过的
Code Display
template<const int N>struct Matrix{
int a[N][N]; Matrix(){memset(a,0,sizeof(a));}
Matrix operator *(const Matrix &b)const{
Matrix res;
rep(i,0,N-1) rep(k,0,N-1) rep(j,0,N-1) ckadd(res.a[i][j],mul(a[i][k],b.a[k][j]));
return res;
}
inline void init(){
for(int i=0;i<N;++i) a[i][i]=1;
return ;
}
inline Matrix qpow(int n){
Matrix b,c=*this; b.init();
while(n){
if(n&1) b=b*c;
n>>=1; c=c*c;
} return b;
}
};
const int inv2=(mod+1)/2,inv3=ksm(3,mod-2);
inline int calc(int x){
//sigma i in [1,x] 1/(3^i)
return mul(inv2,del(1,ksm(inv3,x)));
}
signed main(){
freopen("stable.in","r",stdin); freopen("stable.out","w",stdout);
int T=read(); while(T--){
int n=read(),m=(n-1)/2,ans=inv2; //first type
ckadd(ans,calc(m)); //third type
ckdel(ans,del(1,ksm(inv3,m))); //sixth type
ckadd(ans,mul(2,del(1,ksm(mul(2,inv3),m))));
Matrix<3>a; //fourth type
a.a[0][2]=a.a[1][0]=a.a[2][2]=1;
a.a[0][1]=a.a[1][1]=inv3;
a=a.qpow(n);
ckadd(ans,add(mul(4,mul(mul(inv3,inv3),a.a[1][2])),mul(inv3,a.a[0][2])));
ckdel(ans,calc(n/2));
Matrix<4>b; //fifth type
b.a[1][0]=b.a[2][1]=b.a[3][3]=1;
b.a[0][3]=b.a[2][3]=b.a[0][2]=b.a[2][2]=inv3;
ckadd(ans,b.qpow(n).a[2][3]);
Matrix<5>c; //seventh type
c.a[1][0]=c.a[2][1]=c.a[3][2]=c.a[4][4]=1;
c.a[0][3]=c.a[1][3]=c.a[3][3]=inv3;
c.a[0][4]=c.a[1][4]=c.a[3][4]=inv3;
ckadd(ans,c.qpow(n).a[3][4]);
c.a[3][3]=c.a[3][4]=0;
ckdel(ans,c.qpow(n).a[3][4]);
c.a[3][3]=c.a[3][4]=inv3;
c.a[1][3]=c.a[1][4]=0;
ckdel(ans,c.qpow(n).a[3][4]);
c.a[1][3]=c.a[1][4]=inv3;
c.a[0][3]=c.a[0][4]=0;
ckdel(ans,c.qpow(n).a[3][4]);
ckadd(ans,calc(n/4));
print(add(ans,1));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律