某古 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}{\operatorname{size}(S)}\sum_{x\in S}\max(0,a_i-a_x)=\dfrac{1}{\operatorname{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(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=1+\dfrac{1}{\operatorname{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}{\operatorname{size}(S^{'})+1}(E_{i+1}+\sum_{j\in S^{'}}E_j) \]

\[E_{i+1}=(\operatorname{size}(S^{'})+1)(E_i-1)-\sum_{j\in S^{'}}E_j \]

\[E_{i+1}=E_1\times (A_i(\operatorname{size}(S^{'})+1)-\sum_{j\in S^{'}}A_j)+(\operatorname{size}(S^{'})+1)(B_i-1)-\sum_{j\in S^{'}}B_j \]

\[A_{i+1}=A_i(\operatorname{size}(S^{'})+1)-\sum_{j\in S^{'}}A_j \]

\[B_{i+1}=(\operatorname{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 还是有进步的?但愿如此。

原文地址:https://www.cnblogs.com/lsq147/p/13697104.html