ICPC2021上海

热身赛

A

分类讨论+贪心

#include<bits/stdc++.h>
using namespace std;

const int N=100005;
int n,cnt;
double y[N],d[N],ans,tot_d,ans_d,di;
double dis(int i,int j){
	return sqrt(1.0*(j-i)*(j-i)+(y[j]-y[i])*(y[j]-y[i]));
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
	cin>>y[i];
	y[0]=0;y[n+1]=0;
	for(int i=1;i<=n+1;++i){
		tot_d+=dis(i-1,i);
	}
	//move i,i+1
	for(int i=1;i<n;++i){
		di=(dis(i-1,i+2))-(dis(i-1,i)+dis(i,i+1)+dis(i+1,i+2));
		if(di<ans_d) ans_d=di;
	}
	//move i,j |i-j|>1
	for(int i=1;i<=n;++i){
		d[i]=dis(i-1,i+1)-(dis(i-1,i)+dis(i,i+1));
	}
	double d_min=d[1];
	for(int i=3;i<=n;++i){
		d_min=min(d_min,d[i-2]);
		di=d_min+d[i];
		if(di<ans_d) ans_d=di;
	}
	ans=tot_d+ans_d;
	printf("%lf",ans);
	return 0;
}

B

题目大意

对于一个矩阵,初始全为0,每个时刻每个格子 \(a_{i,j}\) 会增加 \(A_i\times B_j\)
\(q\) 次操作,每次在时刻 \(t_i\) 擦除一行或一列。
求擦除的数字的总和。

Solution

对于每个格子,其对答案的贡献为:最后擦除时刻\(t_{i,j}\times A_i\times B_j\)
\(ans=\sum A_i\times B_j\times t_{i,j}\\=\sum_{t}t\times(\sum_{t_{Ai}\;\leq\;t}A_i*\sum_{t_{Bj}\;=\;t}B_j+\sum_{t_{Ai}\;=\;t}A_i*\sum_{t_{Bj}\;\leq\;t}B_j)\)
将矩阵重组,使得行列各按最后擦除时刻升序排列,分别维护当前时刻 \(t\) 可以擦除(即不会在后面时刻被擦除)的行、列前缀和(即 \(\sum_{t_{Ai}\;\leq\;t}A_i\;,\;\sum_{t_{Bj}\;\leq\;t}B_j)\))。

#include<bits/stdc++.h>
using namespace std;

const int N=100005,mod=(1e9+7);
int n,m,q;
struct matrix{
	long long x,t;
}a[N],b[N];
bool cmp(matrix a,matrix b){
	return a.t<b.t;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i].x;a[i].x%=mod;
	}
	for(int j=1;j<=m;++j){
		cin>>b[j].x;b[j].x%=mod;
	}
	cin>>q;
	int ty,x,t;
	while(q--){
		cin>>ty>>x>>t;
		if(ty==1) a[x].t=t;
		else b[x].t=t;
	}
	sort(a+1,a+1+n,cmp);
	sort(b+1,b+1+m,cmp);
	a[n+1].t=b[m].t+1;
	b[m+1].t=a[n].t+1;
	
	long long sa=0,sb=0,ans=0;
	int i=1,j=1;
	while(i<=n&&a[i].t==0){
		sa=(sa+a[i].x)%mod;++i;
	}
	while(j<=m&&b[j].t==0){
		sb=(sb+b[j].x)%mod;++j;
	}
	while(i<=n||j<=m){
	 	long long t=min(a[i].t,b[j].t);
	 	for(int k=i;k<=n&&a[k].t==t;++k){
	 		sa=(sa+a[k].x)%mod;
		}
	 	
	 	for(int k=j;k<=m&&b[k].t==t;++k){
	 		sb=(sb+b[k].x)%mod;
		}
	 		
		while(i<=n&&a[i].t==t){
			ans=(ans+a[i].x*sb%mod*a[i].t%mod)%mod;
			++i;
		}
		while(j<=m&&b[j].t==t){
			ans=(ans+b[j].x*sa%mod*b[j].t%mod)%mod;
			++j;
		}
	}
	cout<<ans;
	return 0;
}

正式赛

E

题目大意

给定一个数列,选出尽可能多的数,满足两两之差大于k,求最多能取到的数字个数。

Solution

数列排个序,贪心。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=100005;

ll a[N],ans;
int n,k;

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;++i)
	    cin>>a[i];
	sort(a+1,a+1+n);
	
	ll lst=a[1];ans=1;
	for(int i=2;i<=n;++i)
	    if(a[i]-lst>=k){
			lst=a[i];++ans;
		}
	cout<<ans;
}

D

题目大意

给定\(p,q\), 求满足\(\frac{p}{q}=\frac{a}{b}+\frac{b}{a}\)的a,b。

Solution

等价于\(\begin{cases}ab=4kq\\a^2+b^2=4kp\end{cases}\)

\(\begin{cases}(a+b)^2=4k(p+2q)\\(a-b)^2=4k(p-2q)\end{cases}\),

亦即\(\begin{cases}a=\sqrt{k(p+2q)}+\sqrt{k(p-2q)}\\b=\sqrt{k(p+2q)}-\sqrt{k(p-2q)}\end{cases}\)

有整数解当且仅当\((p+2q)(p-2q)\)是完全平方数。

为使解最小,即k去掉完全平方数的因子,可行a,b去除gcd。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=10005,M=10005;
int pri[M],cnt;
bool b[N];
bool issqr(ll x){
	ll y=sqrt(x);
	return y*y==x;
}
void prime(){
	for(int i=2;i*i<=10000;++i){
		for(int j=2;i*j<=10000;++j)
			b[i*j]=true;
	}
	for(int i=2;i<=10000;++i)
		if(!b[i]) pri[++cnt]=i;
}

int main()
{
	int t,p,q;
	cin>>t;
	prime();
	while(t--){
		cin>>p>>q;
		int tmp1=p+(q<<1);
		int tmp2=p-(q<<1);
		if(!issqr(1ll*tmp1*tmp2)){
			printf("%d %d\n",0,0);
		}
		else{
			int k=tmp1;
			for(int i=1;i<=cnt&&pri[i]*pri[i]<=k;++i){
				if(!(k%(pri[i]*pri[i]))) k/=(pri[i]*pri[i]);
			}
			long long d1=sqrt(1ll*k*tmp1),d2=sqrt(1ll*k*tmp2);
			
			printf("%lld %lld\n",(d1+d2)/__gcd(d1+d2,d1-d2),(d1-d2)/__gcd(d1+d2,d1-d2));
		}
	}
}

I

简单dp

#include<bits/stdc++.h>
using namespace std;

const long long inf=1e12;
const int N=105,K=2600;
long long f[N][(K<<1)+10][N],v[N];
bool g[N][(K<<1)+10][N];
int t[N],n,m;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>v[i]>>t[i];
	for(int i=1;i<=n;++i)
		for(int j=0;j<=(K<<1);++j)
			for(int k=0;k<=m;++k)
				f[i][j][k]=-inf;
	f[1][K][0]=0ll;
	f[1][t[1]+K][0]=v[1];
	f[1][-t[1]+K][0]=v[1];
	f[1][t[1]*2+K][1]=v[1];
	f[1][-t[1]*2+K][1]=v[1];
	for(int i=2;i<=n;++i)
		for(int j=0;j<=(K<<1);++j)
			for(int k=0;k<=m;++k){
				if(f[i-1][j][k]!=-inf) f[i][j][k]=max(f[i][j][k],f[i-1][j][k]);
				if(j+t[i]<=(K<<1)&&f[i-1][j+t[i]][k]+v[i]!=-inf) f[i][j][k]=max(f[i][j][k],f[i-1][j+t[i]][k]+v[i]);
				if(j-t[i]>=0&&f[i-1][j-t[i]][k]+v[i]!=-inf) f[i][j][k]=max(f[i][j][k],f[i-1][j-t[i]][k]+v[i]);
				if(k&&j+(t[i]<<1)<=(K<<1)&&f[i-1][j+(t[i]<<1)][k-1]+v[i]!=-inf) f[i][j][k]=max(f[i][j][k],f[i-1][j+(t[i]<<1)][k-1]+v[i]);
				if(k&&j-(t[i]<<1)>=0&&f[i-1][j-(t[i]<<1)][k-1]+v[i]!=-inf) f[i][j][k]=max(f[i][j][k],f[i-1][j-(t[i]<<1)][k-1]+v[i]);
			}
	long long ans=0ll;
	for(int k=0;k<=m;++k)
		ans=max(ans,f[n][K][k]);
	cout<<ans;
	return 0;
}

G

dfs贪心,\(2n\) 个点两两配对的方案数为\((n-1)(n-3)···1\)

#include<bits/stdc++.h>
using namespace std;

const int N=100005;
const long long mod=998244353;
struct graph{
	int nxt,to;
}e[N<<1];
int g[N],deg[N],dep[N],cnt,n;
bool b[N],leaf[N];
long long f[N],ans=1;
void adde(int x,int y){
	e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y;
}
void dfs(int u){
	leaf[u]=true;
	for(int i=g[u];i;i=e[i].nxt){
		if(!dep[e[i].to]){
			leaf[u]=false;
			dep[e[i].to]=dep[u]+1;
			dfs(e[i].to);
			if(b[e[i].to]) --deg[u];
		}
	}
	if(leaf[u]) return;
	if(deg[u]&1) deg[u]--;
	else b[u]=true;
}
int main(){
	f[0]=1;
	for(int i=2;i<N;i+=2)
		f[i]=1ll*(i-1)*f[i-2]%mod;
    scanf("%d",&n);
    for(int i=1,x,y;i<n;++i){
    	scanf("%d%d",&x,&y);
    	adde(x,y);adde(y,x);
    	++deg[x];++deg[y];
	}
	dep[1]=1;
	dfs(1);
	for(int i=1;i<=n;++i)
		if(!leaf[i]){
			ans=(ans*f[deg[i]])%mod;
		}
	cout<<ans;
}

H

题目大意

n个城市,每个城市有活力值,每条边有权值。
所拥有的活力值大于边权的时候可以通过,通过一条边不消耗活力值。
每个城市只能打一次工,打工无门槛,可以获得这个城市的活力值。
q次询问,求给定初始城市和初始活力值能达到的最大活力值。

Solution

离线处理:对并查集过程的各状态建个树,边权为通过这条边到达父亲需要的初始活力值(负数和0都相当于没有)。
对于每个查询,倍增跳到能到达的最高点,以其为根的子树的活力值+初始活力值即为答案。

#include<bits/stdc++.h>
using namespace std;

const int N=100005,K=19;
struct edge{
	int u,v,w; 
}ed[N];
struct graph{
	int nxt,to,w;
}e[N<<1];
int g[N<<1],s[N<<1],f[N<<1][K],w[N<<1][K];
int a[N],fa[N],p[N],n,m,q,cnt,p_cnt;
bool cmp(edge a,edge b){
	return a.w<b.w;
}
int gf(int u){
	if(fa[u]==u) return u;
	return fa[u]=gf(fa[u]);
}
void adde(int u,int v,int w){
	e[++cnt].nxt=g[u];g[u]=cnt;e[cnt].to=v;e[cnt].w=w;
}
void dfs(int u){
	for(int i=g[u];i;i=e[i].nxt){
		dfs(e[i].to);
		s[u]+=s[e[i].to];
		e[i].w-=s[e[i].to];
	}
	if(u<=n) s[u]+=a[u];
}
void dfs1(int u){
	for(int i=1;i<K;++i){
		f[u][i]=f[f[u][i-1]][i-1];
		w[u][i]=max(w[u][i-1],w[f[u][i-1]][i-1]);
	}
	for(int i=g[u];i;i=e[i].nxt){
		f[e[i].to][0]=u;
		w[e[i].to][0]=e[i].w;
		dfs1(e[i].to);
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<=m;++i)
		scanf("%d%d%d",&ed[i].u,&ed[i].v,&ed[i].w);
	
	for(int i=1;i<=n;++i){
		fa[i]=p[i]=i;
	}
	p_cnt=n;
	sort(ed+1,ed+1+m,cmp);
	for(int i=1;i<=m;++i){
		if(gf(ed[i].u)!=gf(ed[i].v)){
			++p_cnt;
			adde(p_cnt,p[fa[ed[i].u]],ed[i].w);
			adde(p_cnt,p[fa[ed[i].v]],ed[i].w);
			fa[gf(ed[i].u)]=gf(ed[i].v);
			p[gf(ed[i].u)]=p_cnt;
		}
	}
	
	dfs(p_cnt);dfs1(p_cnt);
	
	int x,k;
	while(q--){
		scanf("%d%d",&x,&k);
		if(k<w[x][0]){
			printf("%d\n",k+a[x]);
			continue;
		}
		for(int i=K-1;i>=0;--i){
			if(k>=w[x][i]&&f[x][i]){
				x=f[x][i];
			}
		}
		printf("%d\n",k+s[x]);
	}
	
	return 0;
}

K

题目大意

求一个长度为n的01串,每个时刻都不全为0,每个1都会往两边走,相撞或出界就消失,要求2n时刻内出现循环。

Solution

构造题。
\(n=2\)时,\(10\)\(01\)
\(n=3\)时,无解;
\(n\)为偶数时,\(10、01\)交替拼接;
\(n\)为奇数时,\(010+(10+01)_k\)

#include <bits/stdc++.h>
using namespace std;

int n;
int main(){
	cin>>n;
	if(n&1){
		if(n==3){
			cout<<"Unlucky";
			return 0;
		}
		cout<<"010";
		for(int i=1;i<=((n-3)>>1);++i)
			if(i&1) cout<<"10";
			else cout<<"01"; 
	}
	else{
		for(int i=1;i<=(n>>1);++i)
			if(i&1) cout<<"01";
			else cout<<"10"; 
	}
	return 0;
}
原文地址:https://www.cnblogs.com/AireenYe/p/15765191.html