Preface
这场打的可以说发挥了水平但没有完全发挥的说
ABC都是一眼一遍过,结果D是我最怕的期望题,大概推了个DP的式子但感觉成环了初始条件又不够(flag)就弃了
本来当时看E只有十几个人过的,抱着随便看看的心态看了眼题面发现是个套路题
结果30minRush完代码后一直连着T了好多发,直到比赛结束前两分钟才卡常卡过去
嘛不过不管怎么说应该不会掉分的说(最近两场的分怎么被CF给吃掉了,还没更新上去就难绷)
A. Flip Flop Sum
SB题,直接按题意枚举即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],pfx[N],suf[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (pfx[0]=0,i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
for (suf[n+1]=0,i=n;i>=1;--i) suf[i]=suf[i+1]+a[i];
int ans=-1e9; for (i=1;i<n;++i) ans=max(ans,pfx[i-1]+suf[i+2]-a[i]-a[i+1]);
printf("%d\n",ans);
}
return 0;
}
B. The Forbidden Permutation
SB题,话说这题题面写的很难懂赛后被好多人喷来着
不难发现由于只要有一个位置不合法即可,我们先特判掉答案为\(0\)的情况
然后考虑直接枚举每个位置,分别考虑破坏不等式两边所需要的最小步数即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e9;
int t,n,m,d,p[N],pos[N],a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%d%d",&n,&m,&d),i=1;i<=n;++i)
scanf("%d",&p[i]),pos[p[i]]=i;
for (i=1;i<=m;++i) scanf("%d",&a[i]);
bool flag=0; int ans=INF; for (i=1;i<m;++i)
{
int x=pos[a[i]],y=pos[a[i+1]];
if (y<x||y>x+d) { flag=1; break; }
ans=min(ans,y-x); if (n-1>d) ans=min(ans,d+1-(y-x));
}
if (flag) puts("0"); else printf("%d\n",ans);
}
return 0;
}
C. Flexible String
爆搜题
不难发现关于答案的计算方式,我们只要考虑找出每段连续的两个串对应位置相同的区间长度即可
然后注意到题目中反复提到的字符集大小小于等于\(10\)的限制,可以直接爆搜改了哪些字符然后check即可
复杂度\(O(2^{10}\times n)\),实际情况前面是\(C_{10}^k\)只会更小
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,k,tot,rst[26]; char a[N],b[N]; bool vis[N],cs[15];
vector <int> pos[15]; long long ans;
inline long long C2(CI x)
{
return 1LL*x*(x+1)/2LL;
}
inline void DFS(CI now=1,CI num=0)
{
if (num>k) return; if (now==tot+1)
{
RI i; for (i=1;i<=n;++i) vis[i]=a[i]==b[i];
for (i=1;i<=tot;++i) if (cs[i])
for (int x:pos[i]) vis[x]=1;
long long ret=0; int lst=0;
for (i=1;i<=n;++i) if (!vis[i]) ret+=C2(i-1-lst),lst=i;
ret+=C2(n-lst); ans=max(ans,ret); return;
}
cs[now]=1; DFS(now+1,num+1); cs[now]=0; DFS(now+1,num);
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (tot=0,i=0;i<26;++i) rst[i]=0;
for (i=0;i<15;++i) pos[i].clear();
for (scanf("%d%d%s%s",&n,&k,a+1,b+1),i=1;i<=n;++i)
{
if (!rst[a[i]-'a']) rst[a[i]-'a']=++tot;
pos[rst[a[i]-'a']].push_back(i);
}
ans=0; DFS(); printf("%lld\n",ans);
}
return 0;
}
D. Flexible String Revisit
DP题,方程很好写但是求解需要技巧
不难发现我们只关心两个串不同的位置的个数时多少,因此直接设\(f_{i}\)表示有\(i\)个不同位置的期望步数
转移方程显然有:\(f_i=f_{i+1}\times\frac{n-i}{n}+f_{i-1}\times \frac{i}{n}+1\)
即我们对当前状态进行操作,有\(\frac{i}{n}\)的概率选中一个不同的位,这样还要操作\(f_{i-1}\)次,反之则有\(\frac{n-i}{n}\)的概率还要操作\(f_{i+1}\)次
然后这个DP看似是成环的,当时我只发现了\(f_0=0\)这一个终止条件,但没有注意到\(f_n=f_{n-1}+1\)这个特殊情况
有了这个边界之后我们就可以考虑解方程了,因为那个转移的式子可以写成:
\[f_{i-1}=f_{i}\times\frac{n-i+1}{n}+f_{i-2}\times \frac{i-1}{n}+1\\ \Leftrightarrow f_i=(f_{i-1}-1-f_{i-2}\times \frac{i-1}{n})\times \frac{n}{n-i+1} \]这是一个关于前两项的递推式,而根据我们的两个已知条件式可以解出来的
我们不妨设\(f_1\)是已知的初始条件,最后我们可以把每个\(f_i\)都用一个二元组\((x,y)\)来表示,其中\(f_i=x\times f_1+y\)
这样我们在得到了\(f_n,f_{n-1}\)的表示后可以反解出\(f_1\)的值,从而推出其中的任意一项了
不得不说这个trick之前没有见过,算是长见识了的说
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005,mod=998244353;
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
return x-y<0?x-y+mod:x-y;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
struct Data // x*f[1]+y
{
int x,y;
inline Data(CI X=0,CI Y=0)
{
x=X; y=Y;
}
friend inline Data operator + (const Data& A,const Data& B)
{
return Data(sum(A.x,B.x),sum(A.y,B.y));
}
friend inline Data operator - (const Data& A,const Data& B)
{
return Data(sub(A.x,B.x),sub(A.y,B.y));
}
friend inline Data operator * (const Data& A,const Data& B)
{
return Data(1LL*A.x*B.x%mod,1LL*A.y*B.y%mod);
}
}f[N]; int t,n,invn,dif; char a[N],b[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%s%s",&n,a+1,b+1),dif=0,i=1;i<=n;++i) dif+=a[i]!=b[i];
for (f[0]=Data(0,0),f[1]=Data(1,0),invn=quick_pow(n),i=2;i<=n;++i)
{
int tmp=1LL*(i-1)*invn%mod,div=quick_pow(sub(1,tmp));
f[i]=(f[i-1]-Data(0,1)-(f[i-2]*Data(tmp,tmp)))*Data(div,div);
}
Data tmp=f[n]-f[n-1]; int f1=1LL*sub(1,tmp.y)*quick_pow(tmp.x)%mod;
printf("%d\n",sum(1LL*f[dif].x*f1%mod,f[dif].y));
}
return 0;
}
E. The Tree Has Fallen!
套路题
一堆数里选一些出来求最大的异或和,这个一眼线性基没跑了
换根操作的话一眼先定根然后再分类讨论,顺着这个思路这题就很简单了
首先假定\(1\)为根,然后我们考虑在这个有根树上把根换成\(r\)对于\(v\)的影响:
- 当\(r\)不在\(v\)的子树中时,不难发现此时我们要找答案的数集就还是\(v\)的子树
- 当\(r\)在\(v\)的子树中时,手玩一下我们发现,设\(w\)为\(v\)的直接儿子,并且满足\(r\)在\(w\)的子树中,则此时我们要找答案的数集就是所有数的集合减去\(w\)的子树
因此我们把数的DFS序求出来,这样就变成了区间询问的题目了,由于线性基支持合并,因此直接上线段树即可
但是由于\(O(n\log^2 n)\)的复杂度且由于我各部分的封装实现,常数巨大,卡了好久的常数才跑过
后来比赛结束后躺在床上转念一想由于只有查询操作,且两个区间合并时有重复不会影响答案(因为重复的部分相当于插入了两次线性基,对答案没有影响),因此可以用ST表来写
不过由于我太懒的说既然卡过了就万事大吉了,就懒得再写了
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,P=30;
int t,n,a[N],x,y,q,dep[N],dfn[N],L[N],R[N],tot; vector <int> v[N];
struct LB
{
int v[P];
inline LB(void) { memset(v,0,sizeof(v)); }
inline void clear(void) { memset(v,0,sizeof(v)); }
inline void insert(int x)
{
for (RI i=P-1;~i;--i) if ((x>>i)&1)
{
if (!v[i]) return (void)(v[i]=x); x^=v[i];
}
}
inline int query(int x=0)
{
for (RI i=P-1;~i;--i) if ((x^v[i])>x) x^=v[i]; return x;
}
friend inline LB operator + (const LB& A,const LB& B)
{
LB C; RI i; for (i=0;i<P;++i) C.v[i]=A.v[i];
for (i=0;i<P;++i) if (B.v[i]) C.insert(B.v[i]); return C;
}
};
class Segment_Tree
{
private:
LB tr[N<<2];
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void pushup(CI now)
{
tr[now]=tr[now<<1]+tr[now<<1|1];
}
public:
inline void build(TN)
{
if (l==r) return tr[now].clear(),tr[now].insert(a[dfn[l]]);
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline LB query(CI beg,CI end,TN)
{
if (beg>end) return LB(); if (beg<=l&&r<=end) return tr[now]; int mid=l+r>>1;
if (beg<=mid&&end<=mid) return query(beg,end,LS);
if (end>mid&&beg>mid) return query(beg,end,RS);
return query(beg,end,LS)+query(beg,end,RS);
}
#undef TN
#undef LS
#undef RS
}SEG;
class Tree_Solver
{
private:
int anc[N][20];
public:
inline void DFS(CI now=1,CI fa=0)
{
RI i; for (i=0;i<20;++i) anc[now][i]=0;
dep[now]=dep[anc[now][0]=fa]+1; dfn[++tot]=now; L[now]=tot;
for (i=0;i<19;++i) if (anc[now][i]) anc[now][i+1]=anc[anc[now][i]][i]; else break;
for (int to:v[now]) if (to!=fa) DFS(to,now); R[now]=tot;
}
inline void jump(int& x,CI y)
{
for (RI i=20;~i;--i) if ((y>>i)&1) x=anc[x][i];
}
}T;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),v[i].clear();
for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
for (tot=0,T.DFS(),SEG.build(),scanf("%d",&q),i=1;i<=q;++i)
{
if (scanf("%d%d",&x,&y),x==y) printf("%d\n",SEG.query(1,n).query()); else
{
if (L[y]<=L[x]&&L[x]<=R[y])
{
T.jump(x,dep[x]-dep[y]-1);
printf("%d\n",(SEG.query(1,L[x]-1)+SEG.query(R[x]+1,n)).query());
} else printf("%d\n",SEG.query(L[y],R[y]).query());
}
}
}
return 0;
}
标签:CI,const,int,Codeforces,848,return,inline,Div,RI
From: https://www.cnblogs.com/cjjsb/p/17087534.html