The 2019 ICPC Asia Shanghai Regional Contest
链接:https://ac.nowcoder.com/acm/contest/4370
B. Prefix Code (签到题)
题意:给定 n 个串,问是否存在两个串满足其中一个串是另一个串的前缀。不存在输出 Yes,存在输出 No。(字符串最大长度为 10 ,只包含字符 ‘0’ 到 ‘9’)
思路:字典树。在插入的时候直接判断,如果遇到 color > 0 的位置,说明原来有个串是当前串的前缀。如果没有重新创建新的节点,说明当前串是原来串的前缀。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int t,n;
string s;
int trie[maxn][20],color[maxn],no;
bool insert(string s)
{
int len=s.size(),p=0;
bool ok=1;
int last=no;
for(int i=0; i<len; ++i)
{
int c=s[i]-'0';
if(!trie[p][c]) trie[p][c]=++no;
p=trie[p][c];
if(color[p]) ok=0;
}
if(last==no) ok=0;
color[p]++;
return ok;
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
memset(trie,0,sizeof(trie));
memset(color,0,sizeof(color));
no=0;
scanf("%d",&n);
bool ok=1;
for(int i=1; i<=n; ++i)
{
cin>>s;
if(!insert(s)) ok=0;
}
printf("Case #%d: %s\n",++Case,ok?"Yes":"No");
}
return 0;
}
D. Spanning Tree Removal (构造题)
题意:给定一个 n 个节点的完全图,每次删掉一棵生成树,问最多能够删除几次。输出删除的方案。
思路:一个完全图有 n ( n − 1 ) 2 \frac {n(n-1)} 2 2n(n−1) 条边,一棵生成树需要 n -1 条边。因此猜测可以删除 n 2 \frac n2 2n 棵树。
- 一开始自己的想法是:在删除第 i 棵树时,保证每个点剩余可用边数 x ,满足 x > n 2 − i x>\frac n2 -i x>2n−i ,然后用并查集随意匹配,然后就错了,只能满足小部分数据
- 正解是 Z 字型删边:先删 i ,然后是 i +1 ,然后是 i - 1,然后是 i + 2 ,依次类推。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int t,n;
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
printf("Case #%d: %d\n",++Case,n/2);
for(int i=0; i<n/2; ++i)
{
int f=1,u=i;
for(int j=1; j<=n-1; ++j)
{
int v=(u+f*j+n)%n;
f*=-1;
printf("%d %d\n",u+1,v+1);
u=v;
}
}
}
return 0;
}
E. Cave Escape (最大生成树)
题意:给定一个 n × m n\times m n×m 的矩阵,起点在 (sx,sy) 终点在 (ex,ey)。每个点都有价值 V i j V_{ij} Vij 。假设从 (a,b) 走到 (c,d)且(c,d)未访问时,可以获得 V a b × V c d V_{ab}\times V_{cd} Vab×Vcd 的价值,只能按相邻的格子走,且一个点可以重复经过,问能够获得的最大价值是多少?( 1 ≤ n , m ≤ 1000 , 1 ≤ V i j ≤ 100 1\le n ,m \le 1000 ,1\le V_{ij} \le 100 1≤n,m≤1000,1≤Vij≤100)
思路:可以重复经过一个点,所以起点和终点是谁都无所谓。
- 首先可以想到每个点向与它相连且权值最大的点连边。然后就会形成很多个联通块,接下来的问题就是,连通块和连通块之间找一条最大的边来相连。
- 这样一套下来,不就是最大生成树吗?那就完事了。
- 自己写的挺卡常的,就学习了一下网上的做法,观察到权值最多只有 10000 。可以按照权值来遍历。(大概是省掉了排序的时间)
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1000+5;
int t,n,m,sx,sy,ex,ey;
int A,B,C,P;
int x[maxn*maxn];
int cnt;
vector<pair<int,int> > e[10010];
int pa[maxn*maxn];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
ll solve()
{
for(int i=1; i<=n*m; ++i) pa[i]=i;
int tot=0;
ll ans=0;
for(int i=10000; i>=0; --i)
{
for(auto x: e[i])
{
int u=x.fi,v=x.se;
int ru=find(u),rv=find(v);
if(ru==rv) continue;
ans+=i;
pa[ru]=rv;
tot++;
if(tot==n*m-1) break;
}
if(tot==n*m-1) break;
}
return ans;
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
for(int i=3; i<=n*m; ++i) x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
cnt=0;
for(int i=0; i<=10000; ++i) e[i].clear();
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=m; ++j)
{
int u=(i-1)*m+j;
int v1=(i-1)*m+j-1;
int v2=(i-2)*m+j;
if(v1>=1&&v1%m!=0) e[x[u]*x[v1]].push_back({u,v1});
if(v2>=1) e[x[u]*x[v2]].push_back({u,v2});
}
}
printf("Case #%d: %lld\n",++Case,solve());
}
return 0;
}
普通写法,时间慢一点。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1000+5;
int t,n,m,sx,sy,ex,ey;
int A,B,C,P;
int x[maxn*maxn];
int cnt;
struct Edge
{
int u,v,w;
bool operator<(const Edge& b) const
{
return w>b.w;
}
} edges[maxn*maxn*4];
int pa[maxn*maxn];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
ll solve()
{
for(int i=1; i<=n*m; ++i) pa[i]=i;
sort(edges+1,edges+1+cnt);
ll ans=0;
int tot=0;
for(int i=1; i<=cnt; ++i)
{
int u=edges[i].u,v=edges[i].v;
int ru=find(u),rv=find(v);
if(ru==rv) continue;
ans+=edges[i].w;
pa[ru]=rv;
tot++;
if(tot==n*m-1) break;
}
return ans;
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
for(int i=3; i<=n*m; ++i) x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
cnt=0;
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=m; ++j)
{
int u=(i-1)*m+j;
int v1=(i-1)*m+j-1;
int v2=(i-2)*m+j;
if(!x[u]) continue;
if(v1>=1&&v1%m!=0&&x[v1]) edges[++cnt]= {u,v1,x[u]*x[v1]};
if(v2>=1&&x[v2]) edges[++cnt]= {u,v2,x[u]*x[v2]};
}
}
printf("Case #%d: %lld\n",++Case,solve());
}
return 0;
}
F. A Simple Problem On A Tree (树链剖分裸题)
题意:给定一颗树,每个点带点权,维护四个操作
- 1 u v w : 将 u 、v 路径上的权值修改为 w
- 2 u v w : 将 u 、v 路径上的权值加上 w
- 3 u v w : 将 u 、v 路径上的权值乘上 w
- 4 u v : 询问 u 、v 路径上点权的立方和,即求 ∑ x w x 3 \sum_x w_x^3 ∑xwx3
思路:树链剖分维护区间的和,平方和,以及立方和
#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
using namespace std;
const int maxn=1e5+5,mod=1e9+7;
int t,n,q;
int val[maxn];
vector<int> e[maxn];
int sum1[maxn<<2],sum2[maxn<<2],sum3[maxn<<2];
int lazyAdd[maxn<<2],lazyMul[maxn<<2];
int fa[maxn],sz[maxn],son[maxn],depth[maxn];
int dfn[maxn],id[maxn],top[maxn],times;
void add(int &x,int y)
{
x+=y;
if(x>=mod) x-=mod;
}
void mul(int &x,int y)
{
x=1ll*x*y%mod;
}
void dfs1(int u)
{
sz[u]=1;
for(auto v: e[u])
{
if(v==fa[u]) continue;
depth[v]=depth[u]+1;
fa[v]=u;
dfs1(v);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int x)
{
dfn[u]=++times;
id[times]=u;
top[u]=x;
if(!son[u]) return;
dfs2(son[u],x);
for(auto v: e[u])
{
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
void solve(int rt,int len,int a,int b)
{
mul(lazyMul[rt],a);
mul(lazyAdd[rt],a);
add(lazyAdd[rt],b);
if(a!=1)
{
mul(sum1[rt],a);
mul(sum2[rt],1ll*a*a%mod);
mul(sum3[rt],1ll*a*a%mod*a%mod);
}
if(b!=0)
{
int b2=1ll*b*b%mod,b3=1ll*b2*b%mod;
add(sum3[rt],1ll*len*b3%mod);
add(sum3[rt],3ll*sum2[rt]%mod*b%mod);
add(sum3[rt],3ll*sum1[rt]%mod*b2%mod);
add(sum2[rt],2ll*sum1[rt]*b%mod);
add(sum2[rt],1ll*len*b2%mod);
add(sum1[rt],1ll*len*b%mod);
}
}
void pushUp(int rt)
{
sum1[rt]=(sum1[ls]+sum1[rs])%mod;
sum2[rt]=(sum2[ls]+sum2[rs])%mod;
sum3[rt]=(sum3[ls]+sum3[rs])%mod;
}
void pushDown(int rt,int L,int R)
{
int mid=(L+R)>>1;
int a=lazyMul[rt],b=lazyAdd[rt];
solve(ls,mid-L+1,a,b);
solve(rs,R-mid,a,b);
lazyAdd[rt]=0;
lazyMul[rt]=1;
}
void build(int rt,int L,int R)
{
lazyAdd[rt]=0;
lazyMul[rt]=1;
if(L==R)
{
int w=val[id[L]];
sum1[rt]=w;
sum2[rt]=1ll*w*w%mod;
sum3[rt]=1ll*sum2[rt]*w%mod;
return;
}
int mid=(L+R)>>1;
build(ls,L,mid);
build(rs,mid+1,R);
pushUp(rt);
}
void update(int rt,int l,int r,int L,int R,int a,int b)
{
if(l<=L&&R<=r)
{
solve(rt,R-L+1,a,b);
return;
}
pushDown(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid) update(ls,l,r,L,mid,a,b);
if(r>mid) update(rs,l,r,mid+1,R,a,b);
pushUp(rt);
}
int query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r) return sum3[rt];
pushDown(rt,L,R);
int ans=0;
int mid=(L+R)>>1;
if(l<=mid) add(ans,query(ls,l,r,L,mid));
if(r>mid) add(ans,query(rs,l,r,mid+1,R));
pushUp(rt);
return ans;
}
void updatePath(int u,int v,int a,int b)
{
while(top[u]!=top[v])
{
if(depth[top[u]]<depth[top[v]]) swap(u,v);
update(1,dfn[top[u]],dfn[u],1,n,a,b);
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
update(1,dfn[u],dfn[v],1,n,a,b);
}
int queryPath(int u,int v)
{
int ans=0;
while(top[u]!=top[v])
{
if(depth[top[u]]<depth[top[v]]) swap(u,v);
add(ans,query(1,dfn[top[u]],dfn[u],1,n));
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
add(ans,query(1,dfn[u],dfn[v],1,n));
return ans;
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1; i<=n; ++i) e[i].clear(),son[i]=0;
times=0;
for(int i=1; i<=n-1; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1; i<=n; ++i) scanf("%d",&val[i]);
dfs1(1);
dfs2(1,1);
build(1,1,n);
int op,u,v,w;
printf("Case #%d:\n",++Case);
scanf("%d",&q);
while(q--)
{
scanf("%d",&op);
if(op==4)
{
scanf("%d%d",&u,&v);
printf("%d\n",queryPath(u,v));
}
else
{
scanf("%d%d%d",&u,&v,&w);
if(op==1) updatePath(u,v,0,w);
else if(op==2) updatePath(u,v,1,w);
else if(op==3) updatePath(u,v,w,0);
}
}
}
return 0;
}
H. Tree Partition (二分)
题意:给定一颗 n 个节点的树,每个点带点权,让你分成 k 份,使得连通块中最大的点权和最小,输出这个最小值。
思路:显然二分答案。需要同叶节点开始往根走,边走边累积份数。
- 一开始写过拓扑排序,写到一半发现,拓扑排序不能保证根节点和子节点相连,只能用 dfs
- 需要保证的是:根节点和点权和比较小的子节点相连。其余的子节点直接另开一份。字节点返回两个值,一个是子节点构成的份数 cnt 和 以子节点为根构成的点权和 val 。将当前点 u 和子节点返回的较小的点权 val 结合成一份即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+5;
int t,n,k;
vector<int> e[maxn];
int val[maxn];
int tot;
ll now;
pair<ll,ll> dfs(int u,int fa,ll mid)
{
ll cnt=0;
vector<ll> ans;
for(auto v: e[u])
{
if(v==fa) continue;
auto res=dfs(v,u,mid);
ans.push_back(res.se);
cnt+=res.fi;
}
sort(ans.begin(),ans.end());
ll now=val[u];
int n=ans.size();
int pos=-1;
for(int i=0; i<n; ++i)
{
if(now+ans[i]>mid)
{
pos=i;
break;
}
else now+=ans[i];
}
if(pos!=-1) cnt+=n-pos;
return {cnt,now};
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i) e[i].clear();
for(int i=1; i<=n-1; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
ll L=0,R=1e15;
for(int i=1; i<=n; ++i) scanf("%d",&val[i]),L=max(L,1ll*val[i]);
while(L<R)
{
ll mid=(L+R)>>1;
auto res=dfs(1,0,mid);
if(res.se) res.fi++;
if(res.fi>k) L=mid+1;
else R=mid;
}
printf("Case #%d: %lld\n",++Case,L);
}
return 0;
}
K. Color Graph (奇环)
题意:给定一个 n 个点 m 条边的无向联通图。让你对边染色,使得染完色之后不存在奇环,且染的边数最大。输出这个最大的边数
思路:不存在奇环的图是二分图。注意到 n 只有 16 ,显然是想让你暴力。
- 枚举一下一边的点集,然后遍历所有边,统计答案即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=200;
int t,n,m;
int visit[maxn];
pair<int,int> edges[maxn];
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
edges[i]= {u,v};
}
int ans=0;
for(int i=0; i<(1<<n); ++i)
{
memset(visit,0,sizeof(visit));
for(int j=1; j<=n; ++j)
if(i>>j-1&1) visit[j]=1;
int res=0;
for(int j=1; j<=m; ++j)
{
int u=edges[j].first,v=edges[j].second;
if(visit[u]^visit[v]) res++;
}
ans=max(ans,res);
}
printf("Case #%d: %d\n",++Case,ans);
}
return 0;
}