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