ICPC2021-上海


热身赛

A

分类讨论+贪心

#include
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;i1
	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

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
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>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<

正式赛

E

题目大意

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

Solution

数列排个序,贪心。

#include
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<

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
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
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<

G

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

#include
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

H

题目大意

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

Solution

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

#include
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=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 
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;
}