Educational Codeforces Round 40 (Rated for Div. 2)题解

本文是 Educational Codeforces Round 40 的题解。介绍了 D 到 H 题的题意与解法,包括无向图点对数量求解、水龙头放水量计算、迷宫路线方案数计算、城墙小兵分配、树上点对路径长度计数、字符串距离计算等,涉及最短路、贪心、二分、矩阵快速幂、树形 dp、并查集、FFT 等方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一年前的某场教育场,一年后才开始补这套题,发现还是错的非常惨,我好菜啊在这里插入图片描述
感觉这一场题目出得还是非常好的,题目都不是很难,没有搞你很偏的东西,主要是有很多实现的细节,帮助应该还是挺大的,链接:https://codeforces.com/contest/954

  • ABC 大水题就不说了

  • D. Fight Against Traffic

    • 题意:给你一张所有边权都为1的无向图,求出点对 ( x , y ) (x,y) (x,y)的数量,点对满足如下性质:
    1. x , y x,y x,y之间没有边
    2. 加入权值为1的边 ( x , y ) (x,y) (x,y) s s s t t t的最短路径长度不变
    • 解法:分别从 s s s t t t跑一次最短路,就有了到所有点的最短路径,然后枚举点对 ( x , y ) (x,y) (x,y),可以发现,如果加入边 ( x , y ) (x,y) (x,y) s s s t t t的最短路长度减小,则新的最短路径必经过 ( x , y ) (x,y) (x,y),这样就可以求出新的最短路径长度,与未加入该边时的最短路径长度对比,如果更长,则 a n s + + ans++ ans++;
#include<bits/stdc++.h>

using namespace std;

int n,m,u,v,s,t;
bool mp[1005][1005];
int diss[1005],dist[1005];
vector<int> vec[1005];

void dijkstra(int *dis,int s)
{
   priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que;
   que.push(make_pair(0,s));
   for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f;
   dis[s]=0;
   while(!que.empty()){
   		pair<int,int> cur=que.top();que.pop();
   		if(dis[cur.second]<cur.first) continue;
   		for(int i=0;i<vec[cur.second].size();i++){
   			int nxtv=vec[cur.second][i];
   			if(dis[nxtv]>dis[cur.second]+1){
   				dis[nxtv]=dis[cur.second]+1;
   				que.push(make_pair(dis[nxtv],nxtv));
   			}
   		}
   }
}

int main()
{
   scanf("%d %d %d %d",&n,&m,&s,&t);
   for(int i=1;i<=m;i++){
   		scanf("%d %d",&u,&v);
   		vec[u].push_back(v);
   		vec[v].push_back(u);
   		mp[u][v]=mp[v][u]=true;
   }
   dijkstra(diss,s);
   dijkstra(dist,t);
   int path=diss[t],ans=0;
   
   for(int i=1;i<=n;i++){
   		for(int j=i+1;j<=n;j++){
   			int k=min(diss[i]+dist[j]+1,diss[j]+dist[i]+1);
   			if(!mp[i][j]&&k>=path) ans++;
   		}
   }
   printf("%d\n",ans);
}
  • E. Water Taps

    • 题意: n n n个能放温水的水龙头,温度 t i t_i ti,单位时间内最大水量为 x i x_i xi n n n个水龙头的水混合后的温度为 ∑ i = 1 n x i ∗ t i ∑ i = 1 n x i \frac{\sum_{i=1}^{n}{x_i*t_i}}{\sum_{i=1}^{n}{x_i}} i=1nxii=1nxiti给定最后的温度 T T T,求每分钟最多能放出多少水
    • 解法:令上式等于 T T T,先将公式变形: ∑ i = 1 n ( t i − T ) ∗ x i = 0 \sum_{i=1}^{n}{(t_i-T)*x_i}=0 i=1n(tiT)xi=0
      首先将所有水龙头调到最大,然后如果上式 &gt; 0 &gt;0 >0也就说明温度过高,那么贪心的调小最大温度的水龙头,这样才能保证更多的水,同样,如果上式 &lt; 0 &lt;0 <0也就说明温度不够,那么就从温度最低的开始调节
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=2e5+10;

int n,t;

struct node{
	int a,b;
	friend bool operator<(const node &oth1,const node & oth2){
		return oth1.b<oth2.b;
	}
}water[maxn];

int main()
{
	ll tot=0;double ans=0;
	scanf("%d %d",&n,&t);
	for(int i=1;i<=n;i++) scanf("%d",&water[i].a);
	for(int i=1;i<=n;i++){
		scanf("%d",&water[i].b);
		tot+=(ll)(water[i].b-t)*water[i].a;
	}

	sort(water+1,water+n+1);
	if(tot>0){
		for(int i=n;i>=1;i--){
			if(water[i].b==t) {
				ans+=water[i].a;
				continue;
			}
			double cur=(double)tot/(water[i].b-t);
			if(cur>=(double)water[i].a){
				tot-=(ll)(water[i].b-t)*water[i].a;
			}else{
				tot=0;
				ans+=(double)water[i].a-cur;
			}
		}
	}else {
		for(int i=1;i<=n;i++){
			if(water[i].b==t) {
				ans+=water[i].a;
				continue;
			}
			double cur=(double)tot/(water[i].b-t);
			if(cur>=(double)water[i].a){
				tot-=(ll)(water[i].b-t)*water[i].a;
			}else{
				tot=0;
				ans+=(double)water[i].a-cur;
			}
		}
	}
	printf("%.15lf\n",ans);
}
  • F. Runner’s Problem

    • 题意:给你一个 3 ∗ m 3*m 3m迷宫,求从 ( 2 , 1 ) (2,1) (2,1)走到 ( 2 , m ) (2,m) (2,m)的路线方案数,其中有 n n n段墙,第 i i i段在 a i a_i ai行,起点终点分别为 l i , r i l_i,r_i liri
    • 解法:如果没有墙的话显然是一个简单的矩阵快速幂,其矩阵如下所示:
      { 1 1 0 1 1 1 0 1 1 } \left\{ \begin{matrix} 1 &amp; 1 &amp; 0 \\ 1 &amp; 1 &amp; 1 \\ 0 &amp; 1 &amp; 1 \\ \end{matrix} \right\} 110111011
      那么如果某行某列有墙,只用把该矩阵对应该行的列的所有元素变为 0 0 0就可以了,举个栗子,假如第一二行都有墙,那么矩阵变为
      { 0 0 0 0 0 1 0 0 1 } \left\{ \begin{matrix} 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 \\ 0 &amp; 0 &amp; 1 \\ \end{matrix} \right\} 000000011
      所以可以将所有的区间离散处理,然后分段矩阵快速幂就好了(也就是一段区间如果没一点三行的墙位置都不变,那么就可以矩阵快速幂)
    • 函数参数写成 i n t int int爆掉 d e b u g debug debug一下午还能说啥
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn=4;
const ll mod=1e9+7;

ll mat_[][3]={{1,1,0},{1,1,1},{0,1,1}};

template<typename T> 
inline void read(T &x) {
	x = 0;int f = 1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x=x*f;
}

struct matrix{
	ll mat[maxn][maxn];int siz;
	matrix(int a=maxn){
		siz=a-1;
		memset(mat,0,sizeof(mat));
	}
	matrix operator*(const matrix &b){
		matrix res;
		for(int i=1;i<=siz;i++){
			for(int k=1;k<=siz;k++){
				if(mat[i][k]){
					for(int j=1;j<=siz;j++){
						if(b.mat[k][j]){
							res.mat[i][j]=(res.mat[i][j]+(mat[i][k]*b.mat[k][j])%mod)%mod;
						}
					}
				}
			}
		}
		return res;

	}
	matrix pow(long long k){
		matrix res,tmp=*this;
		for(int i=1;i<=siz;i++) res.mat[i][i]=1;
		while(k){
			if(k&1) res=res*tmp;
			tmp=tmp*tmp;
			k>>=1;
		}
		return res;
	}
	void print(){
		for(int i=1;i<=siz;i++){
			for(int j=1;j<=siz;j++){
				printf("%lld%c",mat[i][j],j==siz?'\n':' ');
			}
		}
	}
}a,b,res;

struct node{
	ll l,r;
	node(ll a=0,ll b=0){
		l=a;r=b;
	}
	friend bool operator<(const node &a,const node & b){
		return a.l<b.l;
	}
}seg[4][10005];

int row,tot[4],pre[4],n;
ll l,r,point[4][20005],m,block[10],nxt[10];

void count_(ll column)
{
	memset(block,0,sizeof(block));
	for(int i=1;i<=3;i++){
		int pos=upper_bound(point[i]+1,point[i]+2*tot[i]+1,column)-point[i];
		if(pos%2==0) block[i]=1;
	}

}

matrix get()
{
	matrix res;
	for(int j=1;j<=3;j++){
		for(int i=1;i<=3;i++){
			res.mat[i][j]=block[j]?0:mat_[i-1][j-1];
		}
	}
	return res;
}

int main()
{
	read(n);read(m);
	for(int i=1;i<=3;i++) res.mat[i][i]=1;

	for(int i=1;i<=n;i++) {
		read(row);read(l);read(r);
		seg[row][++pre[row]]=node(l,r+1LL); //+1方便处理,很重要的细节
	}

	for(int i=1;i<=3;i++) sort(seg[i]+1,seg[i]+pre[i]+1);
	for(int i=1;i<=3;i++){
		for(int j=1;j<=pre[i];j++){
			if(seg[i][j].l<=seg[i][tot[i]].r){
				seg[i][tot[i]].r=max(seg[i][tot[i]].r,seg[i][j].r);
			}else{
				seg[i][++tot[i]]=seg[i][j];
			}
		}
	}
	for(int i=1;i<=3;i++){
		for(int j=1;j<=tot[i];j++){
			point[i][2*j-1]=seg[i][j].l;
			point[i][2*j]=seg[i][j].r;
		}
	}
	ll cur=2;
	while(cur<=m){
		ll pos=2e18;
		for(int i=1;i<=3;i++) {
			int nxt_pos=upper_bound(point[i]+1,point[i]+2*tot[i]+1,cur)-point[i];
			if(nxt_pos==2*tot[i]+1) nxt[i]=m+1LL;
			else nxt[i]=point[i][nxt_pos];
			pos=min(pos,nxt[i]);
		}

		count_(cur);
		matrix cur_mat=get();
		res=res*cur_mat.pow(pos-cur);

		cur=pos;
	}
	printf("%lld\n",res.mat[2][2]);
}
  • G. Castle Defense

    • 题意:一坐城墙分成 n n n段,初始每一段都有一定数量的小兵,位置为 i i i的小兵能攻击的范围是 [ m a x ( 1 , i − r ) , m i n ( n , i + r ) ] [max(1,i-r),min(n,i+r)] [max(1,ir),min(n,i+r)],定义第 i i i段城墙的防御系数为所有能攻击该段城墙的小兵数量,现在给你 k k k只小兵,你要把他们分配进去,求 n n n段最小的防御系数最大
    • 解法:贪心+二分
      二分防御系数,check的时候每次贪心的把需要加入的小兵放到位置 i + r i+r i+r,因为这样对后面的帮助最大,然后维护一下区间和,注意这题的数据应该不能使用线段树,指针维护一下就行了
    • 二分右边界开成1e18又wa了好几发
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=5e5+10;

int n,r;
ll a[maxn],k,init[maxn],b[maxn];

bool check(ll che)
{
	ll pre=0,res=0;
	memset(b,0,sizeof(b));
	for(int i=1;i<=n;i++){
		//pre+=b[i];
		int nxt=min(n,i+r);
		if(init[i]+pre<che){
			b[nxt]+=che-(init[i]+pre);
			
			res+=che-(init[i]+pre);
			pre+=che-(init[i]+pre);
			if(res>k) return false;
		}
		pre-=(i-r<=0?0:b[i-r]);
		
	}
	return res<=k;

}

int main()
{
	scanf("%d %d %lld",&n,&r,&k);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);

	ll pre=0;
	for(int i=1;i<=n;i++){
		pre-=i-r-1<=0?0:a[i-r-1];
		init[i]+=pre;
		pre+=a[i];
	}
	pre=0;
	for(int i=n;i>=1;i--){
		pre+=a[i];
		pre-=i+r+1>n?0:a[i+r+1];
		init[i]+=pre;
	}
	ll l=0,r=2e18,ans=0;
	while(l<r){
		ll mid=(l+r)>>1;
		if(check(mid)) {
			ans=mid;
			l=mid+1;
		}
		else r=mid;
	}
	printf("%lld\n",ans);
}
  • H. Path Counting

    • 题意:给你一棵树,这棵树有如下特征:深度为 i i i的节点都拥有 a i a_i ai个儿子,求树上点对 ( x , y ) (x,y) (x,y)数量使得 x x x y y y的简单路径长度为 j j j(j=1,2…2*n-2),注意(1,2)和(2,1)只算一次
    • 解法:简单树形dp问题,但是当然和普通的还是有区别的,你不能遍历每一个节点的呀,然而你发现每个相同深度的节点特征完全一样,所以直接遍历一条深度为 n n n的链就行了,然后注意滚动数组优化一下,复杂度 O ( n 2 ) O(n^2) O(n2)
    • 我第一发就MLE了
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int maxn=5005;
const ll mod=1e9+7;

int n;ll a[maxn],inv2;
ll dp[2][10005],son[5005][5005],mul[5005],ans[10005];

ll quick_pow(ll a,ll b)
{
	ll res=1ll;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

void dfs(int cur)
{
	if(cur==n+1) return;
	dp[cur%2][0]=1;
	for(int i=0;i<=2*n-2;i++){
		dp[(cur+1)%2][i+2]=(i>n?0:son[cur+1][i])*(a[cur]-1)%mod;
		if(i>0) dp[(cur+1)%2][i+1]=(dp[cur%2][i]+dp[(cur+1)%2][i+1])%mod;
		else dp[(cur+1)%2][1]=1;
	}

	for(int i=1;i<=2*n-2;i++){
		ans[i]=(ans[i]+(dp[cur%2][i]+(i>n?0:son[cur][i]))*mul[cur]%mod)%mod;
	}
	dfs(cur+1);
}

int main()
{
	scanf("%d",&n);mul[1]=1;inv2=quick_pow(2,mod-2);
	for(int i=1;i<n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++) {
		son[i][0]=1;
		mul[i+1]=mul[i]*a[i]%mod;
		for(int j=1;i+j<=n;j++) son[i][j]=son[i][j-1]*a[i+j-1]%mod;
	}
	dfs(1);
	for(int i=1;i<=2*n-2;i++) printf("%lld%c",ans[i]*inv2%mod,i==2*n-2?'\n':' '); 
}
  • H. Path Counting

    • 每次操作可以将两个等长字符串中所有的字符 a a a变成字符 b b b,定义两个等长字符串的距离为:使得两字符串相同的最小操作数,给定字符串 s s s t t t,求s中所有的长度为 t t t的子串与 t t t的距离
    • 解法:并查集或 F F T FFT FFT
      位置对应相同的两个字符之间连边,那么形成的图中两两之间可以转化,那么合并的次数就是两个字符串之间的距离,然而你会发现这个复杂度最高可以达到 4 ∗ 1 0 9 4*10^9 4109,但是每次只有一个简单的赋值操作,常数很小,再加上 C F CF CF评测机那么快,就能水过去了233
      当然官方给出的解法是 F F T FFT FFT,这里也给出了代码
  • 并查集
#pragma GCC optimize("O2")
#pragma GCC optimize("unroll-loops")

#include<bits/stdc++.h>

using namespace std;
const int maxn=125005;

char s[maxn],t[maxn];
int n,m,edge[10][10];

struct dsu{
	int fa[10],rank[10];
	void init(int k){
		for(int i=1;i<=k;i++) fa[i]=i,rank[i]=0;
	}
	int fin(int k){
		return fa[k]==k?k:(fa[k]=fin(fa[k]));
	}
	
	bool unite(int a,int b){
		int x=fin(a),y=fin(b);
		if(x==y) return false;
		if(rank[x]<rank[y]) fa[x]=y;
		else{
			fa[y]=x;
			if(rank[x]==rank[y]) rank[x]++;
		}
		return true;
	}
	
	bool same(int a,int b){
		return fin(a)==fin(b);
	}
}tree;


int main()
{
	scanf("%s %s",s+1,t+1);
	n=strlen(s+1);m=strlen(t+1);

	for(int i=1;i+m-1<=n;i++){
		tree.init(6);int ans=0;
		memset(edge,0,sizeof(edge));
		for(int j=i;j<=i+m-1;j++)  edge[s[j]-'a'+1][t[j-i+1]-'a'+1]=1;
		for(int i=1;i<=6;i++) for(int j=1;j<=6;j++) if(edge[i][j]&&tree.unite(i,j)) ans++;
		printf("%d ",ans); 
	}
}
  • F F T FFT FFT
#include <bits/stdc++.h>
using namespace std;

struct Complex{
	double r, i;
	Complex(double r = 0, double i = 0): r(r), i(i){}
	inline Complex operator +(const Complex& rhs){
		return Complex(r + rhs.r, i + rhs.i);
	}
	inline Complex operator -(const Complex& rhs){
		return Complex(r - rhs.r, i - rhs.i);
	}
	inline Complex operator *(const Complex& rhs){
		return Complex(r * rhs.r - i * rhs.i, i * rhs.r + r * rhs.i);
	}
	inline Complex operator /=(const int& rhs){
		r /= rhs;
		return *this;
	}
}; 

const int N = 5e5;
const long double PI = acos(-1.0);

char s[N], t[N];
int p[10][N];
int sum[N], rev[N];
Complex v1[N], v2[N];

int find(int x, int id){
	return x == p[x][id] ? x : p[x][id] = find(p[x][id], id);
}

bool same(int x, int y, int id){
	return find(x, id) == find(y, id);
}

void unite(int x, int y, int id){
	int fx = find(x, id), fy = find(y, id);
	p[fy][id] = fx;
}

void Fast_Fourier_Transform(Complex *a, int n, int inverse){
	for (int i = 0; i < n; i++){
		if (rev[i] > i) swap(a[i], a[rev[i]]);
	}
	for (int h = 2; h <= n; h <<= 1){
		double ang = inverse * 2 * PI / h;
		Complex wn(cos(ang), sin(ang));
		for (int j = 0; j < n; j += h){
			Complex w(1, 0);
			for (int k = j; k < j + h / 2; k++){
				Complex u = a[k];
				Complex t = w * a[k + h / 2];
				a[k] = u + t;
				a[k + h / 2] = u - t;
				w = w * wn;
			}
		}
	}
	if (inverse == -1){
		for (int i = 0; i < n; i++) a[i] /= n;
	} 
}

int main(){
	int n, m, sz, ans;
	scanf("%s", s);
	n = strlen(s);
	scanf("%s", t);
	m = strlen(t);
	sz = 2;
	while (sz < n + m) sz <<= 1;
	for (int i = 0, j = 0; i < sz; i++){
		rev[i] = j;
		int k = sz;
		while (j & (k >>= 1)) j &= ~k;
		j |= k;
	}
	for (int i = 0; i < n; i++){
		for (int j = 0; j < 6; j++){
			p[j][i] = j;
		}
	}
	for (int i = 0; i < 6; i++){
		for (int j = 0; j < 6; j++){
			if (i == j) continue;
			for (int k = 0; k < sz; k++) v1[k] = v2[k] = Complex(0, 0);
			for (int k = 0; k < n; k++){
				if (s[k] == 'a' + i) v1[k] = Complex(1, 0);
			}
			for (int k = 0; k < m; k++){
				if (t[k] == 'a' + j) v2[m - k - 1] = Complex(1, 0);
			}
			Fast_Fourier_Transform(v1, sz, 1);
			Fast_Fourier_Transform(v2, sz, 1);
			for (int i = 0; i < sz; i++) v1[i] = v1[i] * v2[i];
			Fast_Fourier_Transform(v1, sz, -1);
			for (int k = 0; k < n; k++){
				if (v1[k].r > 0.1){
					if (!same(i, j, k)){
						unite(i, j, k);
						sum[k]++;
					}
				}
			}
		}
	}
	for (int i = m - 1; i < n; i++) printf("%d ", sum[i]);
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值