某古 9 月月賽 I 遊記
目錄
由於部落格已經估了好久沒寫了,所以跑來水更篇部落格。
第一次 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\) 對答案造成的貢獻就是:
\[\dfrac{1}{\mbox{size}(S)}\sum_{x\in S}\max(0,a_i-a_x)=\dfrac{1}{\mbox{size}(S)}\sum_{x\in S}(a_i-a_x)[a_i\ge a_x] \]
相當於就是要求 \([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(1ll*A[a[i]-1]*mus[i]%mod+sum[i])*inv[len]%mod+ans);
}
write(ans),pc('\n');
return 0;
}
D [Cnoi2020]線形生物
題目分析
設 \(E_i\) 表示從 \(i\) 走到 \(n+1\) 的期望步數,設 \(i\) 的到達集合為 \(S\) ,那麼有:
\[E_i=1+\dfrac{1}{\mbox{size}(S)}\sum_{j\in S}E_j \]
不妨設 \(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^{'}\) ,那麼:
\[E_i=1+\dfrac{1}{\mbox{size}(S^{'})+1}(E_{i+1}+\sum_{j\in S^{'}}E_j) \]
\[E_{i+1}=(\mbox{size}(S^{'})+1)(E_i-1)-\sum_{j\in S^{'}}E_j \]
\[E_{i+1}=E_1\times (A_i(\mbox{size}(S^{'})+1)-\sum_{j\in S^{'}}A_j)+(\mbox{size}(S^{'})+1)(B_i-1)-\sum_{j\in S^{'}}B_j \]
\[A_{i+1}=A_i(\mbox{size}(S^{'})+1)-\sum_{j\in S^{'}}A_j \]
\[B_{i+1}=(\mbox{size}(S^{'})+1)(B_i-1)-\sum_{j\in S^{'}}B_j \]
參考程式碼
#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 還是有進步的?但願如此。