由于博客已经估了好久没写了,所以跑来水更篇博客。
第一次 AK div2 ,莫名感动。
A [Cnoi2020]子弦
题目分析
这题难道有比 SAM 还简单的方法???
越短的串出现次数才可能会越多,直接统计每个字符出现的次数,输出最大值即可。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=10000007;
char s[maxn];int cnt[26];
int main(){
scanf("%s",s);int len=strlen(s),ans=0;
for(int i=0;i<len;++i)++cnt[s[i]-'a'];
for(int i=0;i<26;++i)ans=max(ans,cnt[i]);
write(ans),pc('\n');
return 0;
}
B [Cnoi2020]雷雨
题目分析
一条路径肯定是从雷云出发然后从某处分界直到底部,所以我们可以求出任意点到三个点之间的最短路,然后枚举从哪里分界即可。
求最短路时可以不用建出图,直接枚举上下左右就可以了。
我一开始做的时候就直接放弃这种想法了,感觉这个复杂度有点大,但是还是可以跑过。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=1005;
struct Node{
int nx,ny;long long dis;
Node(int nx=0,int ny=0,long long dis=0):
nx(nx),ny(ny),dis(dis){}
bool operator < (const Node o)const{
return dis<o.dis;
}
}heap[maxn*maxn*4];
int sz;
void push(Node o){
heap[++sz]=o;int no=sz;
while(no>1&&o<heap[no>>1])
heap[no]=heap[no>>1],no>>=1;
heap[no]=o;
}
void pop(void){
swap(heap[1],heap[sz--]);int no=1;
while("Imakf ak ioi"){
int nt=no;
if((no<<1)<=sz&&heap[no<<1]<heap[nt])nt=no<<1;
if((no<<1|1)<=sz&&heap[no<<1|1]<heap[nt])nt=no<<1|1;
if(no==nt)break;swap(heap[no],heap[nt]);no=nt;
}
}
Node top(void){
return heap[1];
}
int R[maxn][maxn],vis[maxn][maxn];
long long dis[3][maxn][maxn];
const int dx[4]={0,1,0,-1},
dy[4]={1,0,-1,0};
int n,m;
void solve(int o,int sx,int sy){
memset(dis[o],0x3f,sizeof dis[o]);
memset(vis,0,sizeof vis);
push(Node(sx,sy,dis[o][sx][sy]=R[sx][sy]));
while(sz){
Node tmp=top();int nx=tmp.nx,ny=tmp.ny;pop();
if(vis[nx][ny])continue;vis[nx][ny]=true;
for(int d=0;d<4;++d){
int tx=nx+dx[d],ty=ny+dy[d];
if(tx<1||tx>n||ty<1||ty>m)continue;
long long wi=dis[o][nx][ny]+R[tx][ty];
if(dis[o][tx][ty]<=wi)continue;
push(Node(tx,ty,dis[o][tx][ty]=wi));
}
}
}
int main(){
int a,b,c;
read(n),read(m),read(a),read(b),read(c);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
read(R[i][j]);
solve(0,1,a);solve(1,n,b);solve(2,n,c);
long long ans=0x3f3f3f3f3f3f3f3fll;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ans=min(ans,dis[0][i][j]+dis[1][i][j]+dis[2][i][j]-R[i][j]*2);
write(ans),pc('\n');
return 0;
}
C [Cnoi2020]梦原
题目分析
考虑加入一个点会造成什么样的贡献,如果它的 \(a\) 比它父亲的 \(a\) 值要小,那么在减小它父亲的 \(a\) 的时候就可以顺便减小它自己的 \(a\) ,否则就会增加它们两个差的使用次数。
设 \(i\) 的父亲可以选择的集合为 \(S\) ,那么 \(i\) 对答案造成的贡献就是:
相当于就是要求 \([i-k,i-1]\cap N^+\) 中 \(a\) 值比 \(a_i\) 小的数的个数和这些数 \(a\) 值的和,树套树可能过不了,离线后可以使用树状数组差分求答案。
时间复杂度: \(\mathcal O(n\log_2n)\) 。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=1000006,mod=998244353;
int mo(const int x){
return x>=mod?x-mod:x;
}
int rt[maxn],tr[maxn],cnt;
void change(int pos,int val){
while(pos<=cnt){
tr[pos]=mo(tr[pos]+val);
rt[pos]=mo(rt[pos]+1);
pos+=pos&(-pos);
}
}
int RT;
int query(int pos){
int re=0;RT=0;
while(pos){
re=mo(re+tr[pos]);
RT=mo(RT+rt[pos]);
pos^=pos&(-pos);
}
return re;
}
struct Edge{
int v,id,nt;
Edge(int v=0,int id=0,int nt=0):
v(v),id(id),nt(nt){}
}e[maxn*2];
int hd[maxn],num;
void qwq(int u,int v,int id){
e[++num]=Edge(v,id,hd[u]),hd[u]=num;
}
int a[maxn],A[maxn],inv[maxn],sum[maxn],mus[maxn];
int main(){
int n,k,ans=0;read(n),read(k);
inv[1]=1;
for(int i=2;i<=n;++i)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;++i)read(a[i]),A[i-1]=a[i];ans=a[1];
sort(A,A+n);cnt=unique(A,A+n)-A;
for(int i=1;i<=n;++i)a[i]=lower_bound(A,A+cnt,a[i])-A+1;
for(int i=2;i<=n;++i){
int r=i-1,l=max(1,i-k);
qwq(l-1,a[i],-i);
qwq(r,a[i],i);
}
for(int i=1;i<=n;++i){
change(a[i],A[a[i]-1]);
for(int j=hd[i];j;j=e[j].nt){
int v=e[j].v,id=e[j].id,tmp=query(v);
if(id>0)sum[id]=mo(sum[id]+tmp),mus[id]=mo(mus[id]+RT);
else sum[-id]=mo(mod-tmp+sum[-id]),mus[-id]=mo(mus[-id]-RT);
}
}
for(int i=2;i<=n;++i){
int r=i-1,l=max(1,i-k),len=r-l+1;
ans=mo(1ll*mo(mod-sum[i]+1ll*A[a[i]-1]*mus[i]%mod)*inv[len]%mod+ans);
}
write(ans),pc('\n');
return 0;
}
D [Cnoi2020]线形生物
题目分析
设 \(E_i\) 表示从 \(i\) 走到 \(n+1\) 的期望步数,设 \(i\) 的到达集合为 \(S\) ,那么有:
不妨设 \(E_i=A_iE_1+B_i\) ,那么显然可以从 \(A_1,B_1\) 一直推出 \(A_{n+1},B_{n+1}\) ,那么 \(A_{n+1}E_1+B_{n+1}=E_{n+1}=0\) ,所以 \(E_1=-\dfrac{B_{n+1}}{A_{n+1}}\) 。
具体如何推,不妨设 \(i\) 通过返祖边到达的集合为 \(S^{'}\) ,那么:
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=1000005,maxm=1000005,mod=998244353;
int mo(const int x){return x>=mod?x-mod:x;}
int power(int a,int x){
int re=1;
while(x){
if(x&1)re=1ll*re*a%mod;
a=1ll*a*a%mod,x>>=1;
}
return re;
}
int d[maxn];
struct Edge{
int v,nt;
Edge(int v=0,int nt=0):
v(v),nt(nt){}
}e[maxm];
int hd[maxn],num,A[maxn],B[maxn];
void qwq(int u,int v){
e[++num]=Edge(v,hd[u]),hd[u]=num;
}
int main(){
int id,n,m;
read(id),read(n),read(m);
for(int i=1;i<=m;++i){
int u,v;read(u),read(v);
++d[u];qwq(u,v);
}
A[1]=1,B[1]=0;
for(int i=1;i<=n;++i){
int sA=0,sB=0;
for(int j=hd[i];j;j=e[j].nt){
int v=e[j].v;
sA=mo(sA+A[v]);
sB=mo(sB+B[v]);
}
A[i+1]=mo(mod-sA+1ll*(d[i]+1)*A[i]%mod);
B[i+1]=mo(mod-mo(d[i]+1+sB)+1ll*(d[i]+1)*B[i]%mod);
}
write(1ll*mo(mod-B[n+1])*power(A[n+1],mod-2)%mod),pc('\n');
return 0;
}
小结
噫!好!我 AK 了!
这次的月赛还是很简单的,算是良心赛,可以很好地考察一些选手地卡常技巧(大雾。
可能说明我搞了许久 OI 还是有进步的?但愿如此。