出题三
T1
签到题,无原。
设 \(T\) 为最终的字符串,\(n\) 为字符串 \(S\) 的长度。为了方便计数,我们钦定 \(S\) 的每一位尽可能向 \(T\) 的后面匹配。对于匹配到的每一位,我们分开考虑,假设当前 \(S\) 的第一位匹配到了 \(T\) 的第 \(i\) 位。
对于第 \(i\) 位之前,显然可以任意填,方案为 \(26^{i-1}\);对于 \(S\) 的剩下 \(n-1\) 位,方案数为 \(C_{n+k-i}^{n-1}\);对于最后匹配的位置,由于我们钦定每一位尽可能向后面匹配,所以剩下没填的位置都不能和它上一个填过的位置重合,方案数为 \(25^{k-i+1}\)。最终答案即为:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int N=2e6+10;
int inv[N],fac[N],k,n,ans;
char s[N];
inline int ksm(int a,int b) {
int res=1;
while(b) {
if(b&1) res=res*a%mod;
b>>=1;a=a*a%mod;
}
return res;
}
inline void init() {
n=strlen(s+1);fac[0]=inv[0]=1;
for(int i=1;i<N;++i) fac[i]=i*fac[i-1]%mod;
inv[N-1]=ksm(fac[N-1],mod-2);
for(int i=N-2;i>=1;--i) inv[i]=inv[i+1]*(i+1)%mod;
}
inline int C(int a,int b) {return fac[a]*inv[b]%mod*inv[a-b]%mod;}
signed main() {
scanf("%lld %s",&k,s+1);init();
for(int i=1;i<=k+1;++i) ans+=ksm(26,i-1)*C(n+k-i,n-1)%mod*ksm(25,k-i+1)%mod,ans%=mod;
cout<<ans;
return 0;
}
T2
原 P11390。
子任务一
暴力模拟。
子任务二
这部分和正解没什么关系。
由于值域很小,所以我们可以发现:区间长度为 \(\frac{k(k-1)}{2}\)。
证明:由于只有 \(k\) 种数,每种数的出现次数不能相同,所以每个数出现次数总和为 \(1+2+...+k\),也就是 \(\frac{k(k-1)}{2}\)。双指针维护一下即可。
子任务三
这部分是启发正解的。
对于一个数 \(a_i\),我们设 \(pre_i\) 表示 \(a_i\) 上一次出现的位置,则区间 \((pre_i,i]\) 产生贡献,区间 \(+1\);区间 \((pre_{pre_i},pre_i]\) 贡献消失,区间 \(-1\)。显然可以线段树优化,将题目转化为区间加、查询全局非 \(0\) 个数。
子任务四
我们重新转化一下题意:
记 \(S_i\) 表示存在出现恰好 \(i\) 次的数的线段集合,我们要求的即为 |\(S_1 \cap S_2 \cap... \cap S_k|\)。
思考一下子任务三时我们的做法:区间加、区间查非 \(0\) 数的个数。可以发现这就是矩形面积并。此时题目就相当于求 \(k\) 个矩形面积并的交,容斥一下即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
inline int read() {
int s=0,x=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') x=-1;ch=getchar();}
while(isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*x;
}
struct info{int mi,cnt;};
inline info operator +(info f,info g) {
info res={0,0};res.mi=min(f.mi,g.mi);
res.cnt+=(f.mi==res.mi)*f.cnt+(g.mi==res.mi)*g.cnt;
return res;
}
int n,a[N],k,lst[N],pre[N];
long long ans;
struct SEG{
info tr[N<<3];int tag[N<<3];
#define mid ((l+r)>>1)
#define ls p<<1
#define rs p<<1|1
inline void pushup(int p) {tr[p]=tr[ls]+tr[rs];}
inline void pd(int p,int k) {tr[p].mi+=k,tag[p]+=k;}
inline void pushdown(int p) {pd(ls,tag[p]),pd(rs,tag[p]),tag[p]=0;}
inline void build(int p,int l,int r) {
tag[p]=0;if(l==r) {tr[p]={0,1};return ;}
build(ls,l,mid);build(rs,mid+1,r);pushup(p);
}
inline void add(int p,int l,int r,int x,int y,int k) {
if(l>y||r<x) return ;
if(l>=x&&r<=y) {pd(p,k);return ;}
pushdown(p);add(ls,l,mid,x,y,k);add(rs,mid+1,r,x,y,k);pushup(p);
}
inline int qry() {return tr[1].mi?n:n-tr[1].cnt;}
}t;
int main() {
n=read();k=read();
for(int i=1;i<=n;++i) a[i]=read(),pre[i]=lst[a[i]],lst[a[i]]=i;
for(int j=1,op;j<(1<<k);++j) {
t.build(1,1,n);op=__builtin_popcount(j);
for(int i=1;i<=n;++i) {
int nw=i;
for(int p=0;p<k;++p) {
if(j&(1<<p)) {
t.add(1,1,n,pre[nw]+1,nw,1);
if(pre[nw]) t.add(1,1,n,pre[pre[nw]]+1,pre[nw],-1);
}
nw=pre[nw];if(!nw) break;
}
int lt=t.qry();
ans+=lt*(op&1?1:-1);
}
}
cout<<ans;
return 0;
}
T3
原 P4922。
其实一堆技能里面有一大半是迷惑你的。
瞬身闪避:不如大招。
燃起来了:不如一刀大斩。
劳大肘击:不如大招。
吓哭超影 \(3000\):表面上看有 \(10s\) 时停,实际上,它会清除持续伤害 \(buff\),牢布打一下就后退打不到了,留尼卡多利原地杵 \(10s\),有啥用?不如大招。
弑神登神:不如大招。
综上所述,只有一刀大斩、大招、通灵螃蟹有用。但此时攻击还是太多了,考虑继续简化。
我们考虑大招的位置,设此时持续伤害 \(buff\) 有 \(i\) 层,减速有 \(j\) 层。
1.大招与一刀大斩结合:
大招在前:\(i \times (j+5+1)+(i+1)(j+1)\)
大招在后:\((i+1)(j+1)+(i+1)(j+5+1)\)
显然大招在后伤害更高。
2.大招与通灵螃蟹结合:
大招在前:\(i \times(j+1+5)+i \times(j+2)\)
大招在后:\(i \times(j+2)+i \times(j+5+2)\)
显然也是大招在后伤害更高。
或者你也可以感性理解:另外两个技能会叠 \(buff\),打游戏平时不都是先叠 \(buff\) 再开大嘛。
这时候就有策略了,我们先使用一刀大斩和通灵螃蟹,再开大。
考虑 \(dp\)。设 \(f_{i,j}\) 表示使用了 \(i\) 次一刀大斩和 \(j\) 次通灵螃蟹的最大伤害,则有转移:
\(a\) 指一刀大斩伤害,\(c\) 指通灵螃蟹伤害,\(r\) 指持续伤害 \(buff\)。
此时时间 \(O(n^2)\),空间滚动数组一下即可 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+10;
int a,b,c,ans1,ans2=N,f[2][N],n,hp,atk,r;
signed main() {
cin>>n>>hp>>atk;atk/=10;
a=atk*8,c=atk*7;r=atk;
for(int i=0;i<=n;++i)
for(int j=0;j<=n-i;++j) {
b=atk*12+atk*i*(j+6);
if(i) f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j]+a+r*(j+1)*(i-1));
if(j) f[i&1][j]=max(f[i&1][j],f[i&1][j-1]+c+r*i*j);
ans1=max(ans1,f[i&1][j]+(n-i-j)*b);
if(f[i&1][j]>=hp) ans2=min(ans2,i+j);
else if(f[i&1][j]+(n-i-j)*b>=hp) ans2=min(ans2,i+j+(hp-f[i&1][j]+b-1)/b);
}
ans1<hp?printf("%lld\nhks",ans1):printf("%lld\nLeiZeProMax Nb!!!",ans2);
return 0;
}
T4
原 P12336。
我知道你想骂我,但是你先别急。
看到根号,先考虑平方。此时式子转化为:
由于 \(a,b,c,d\) 均为正整数,所以有 \(a^2+b^2+c^2+d^2>d\),于是就有 \((a \oplus b \oplus c \oplus d)^2>d\)。
设 \(a \oplus b \oplus c \oplus d=d+x\),那么有
考虑如何构造一个 \(x\)。不难发现任何一个 \(x\) 都是可以构造的,设 \(y\) 为 \(x\) 二进制下最高位,则我们可以令 \(d\) 的前 \(y\) 位全为 \(0\),此时只需要构造 \(a \oplus b \oplus c=x\) 即可。
为了构造更方便,我们考虑让 \(x\) 尽可能小。\(0\) 肯定不行,所以我们让 \(x=1\),即 \(a \oplus b \oplus c=1\)(当然前提是 \(d\) \(mod\) \(2=0\)),此时上式可以变为:
此时我们可以表示出 \(d\):
考虑如何构造出 \(a \oplus b \oplus c=1\),为了接下来方便,我们先特判掉 \(a=1\) 的情况,这个可以直接暴力枚举。
那么此时有 \(a>1\) 且 \(a<b<c\)。
考虑异或的性质。显然 \(a,b,c\) 二进制下的最高位不能相同,且 \(b,c\) 最高位相同。
这个时候就需要我们和出题入脑电波交流一下了。我们设 \(k\) 为 \(a\) 在二进制下最高位,令 \(b=a+2^k\),可以发现,此时 \(a \oplus b=2^k+2^{k+1}\),我们再令 \(c=2^k+2^{k+1}+1\),此时我们可以惊奇的发现,\(a \oplus b \oplus c\) 居然成了 \(1\)!并且我们可以发现,这种构造显然满足 \(a<b<c<d\)。
但这就完了吗?不,此时我们发现,我们忽略了这种构造的前提条件:\(d\) \(mod\) \(2=0\),代入发现,当 \(a \equiv 0(mod\) \(2)\) 时,此时满足条件;但当 \(a \equiv 1(mod\) \(2)\) 时,并不满足条件。
考虑如何调整构造方案,此时我们再与出题入进行一波脑电波交流。
我们可以发现 \(a \equiv b(mod\) \(2)\),\(c \equiv 1(mod\) \(2)\),当 \(a \equiv 1(mod\) \(2)\) 时,我们将 \(b,c\) 都减 \(1\),此时可以发现:\(a \oplus (b-1) \oplus(c-1)\) 依然等于 \(1\)。此时我们可以发现,我们满足了 \(d\) \(mod\) \(2=0\) 的条件。
构造完毕。总结一下:
1.当 \(a=1\) 时,暴力或者随便找一组解。
2.当 \(a\) \(mod\) \(2=0\) 时,令 \(k\) 为 \(a\) 在二进制下最高位,则 \(b=a+2^k\),\(c=2^k+2^{k+1}+1,d=\frac{a^2+b^2+c^2-1}{2}\)。
3.当 \(a\) \(mod\) \(2=1\) 时,\(b=a+2^k-1\),\(c=2^k+2^{k+1}\)。
至于无解嘛,你可以发现不存在无解情况。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b,c,d;
signed main() {
cin>>a;int k=__lg(a);
if(a==1) {cout<<"4 28 40\n";return 0;}
if(!(a&1)) b=a+(1ll<<k),c=(1ll<<k)+(1ll<<(k+1))+1,d=(a*a+b*b+c*c-1)/2;
else b=a+(1ll<<k)-1,c=(1ll<<k)+(1ll<<(k+1)),d=(a*a+b*b+c*c-1)/2;
cout<<b<<' '<<c<<' '<<d<<'\n';
return 0;
}