2019 ICPC Shanghai Site记录
K-Color Graph
发现 \(n\) 只有 \(16\),可以爆搜!
考虑到无奇环和二分图互为充要条件,只要暴力枚举在二分图左边还是右边,根据定义看最多能保留多少条边就可以了!
查看代码
#include<bits/stdc++.h>
using namespace std;
int t,n,m,maxn,u[100005],v[100005],color[20005];
vector<int>nbr[20005],_nbr[20005];
void add(int u,int v){
nbr[u].push_back(v);
}
void _add(int u,int v){
_nbr[u].push_back(v);
}
int vis[20];
int ans;
bool dfs(int cur,int c){
color[cur]=c;
for(int i=0;i<_nbr[cur].size();i++){
int nxt=_nbr[cur][i];
if(color[nxt]==3-c)continue;
else if(color[nxt]==c)return 0;
else if(color[nxt]==0){
if(dfs(nxt,3-c)==0)return 0;
}
}
return 1;
}
void work(){
int tot=0;
for(int i=1;i<=m;++i){
if(!vis[u[i]]||!vis[v[i]]){
continue;
}
if(vis[u[i]]==vis[v[i]])continue;
else tot++;
}
ans=max(ans,tot);
}
void dfss(int cur){
if(cur==n+1){
work();
return;
}
vis[cur]=1;
dfss(cur+1);
vis[cur]=2;
dfss(cur+1);
}
int main(){
ios::sync_with_stdio(0);
cin>>t;
int cntt=0;
while(t--){
cin>>n>>m;
ans=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;++i){
cin>>u[i]>>v[i];
}
dfss(1);
cout<<"Case #"<<++cntt<<": "<<ans<<'\n';
}
return 0;
}
D-Spanning Tree Removal
一个完全图,一次删一棵树,问你最多能删多少次。
有一个奇怪的思路就是你可以反复横跳删,先删完左边再删右边最近的没删的(
然后过了。
感性理解这是对的(
查看代码
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
bool vis[1005][1005];
int main(){
ios::sync_with_stdio(0);
cin>>t;
int cnt=0;
while(t--){
cin>>n;
cout<<"Case #"<<++cnt<<": "<<n/2<<'\n';
for(int i=1;i<=n/2;++i){
int k=i;
int flg=1;
for(int j=1;j<n;++j){
cout<<(k+1)<<' '<<(k+flg*j+n)%n+1<<'\n';
k=(k+flg*j+n)%n;
flg*=-1;
}
}
}
return 0;
}
E-Cave Escape
你发现格子可以重复走,只是不能重复得到贡献。
你又发现你不能闪现瞬移,那么路径一定是联通的!
答案要最大!
那就是,最大生成树!
\(n\times m=10^6\),\(O(n\times m\times \log nm)\) 特别的稳啊(
于是便过了,但是要稍微卡卡常(
其实你也可以使用桶排来避免卡常(
因为 \(p\) 的值比较小。
查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,s,xxxx,y;
int _w[1001][1001],x[1000001];
int fa[1000001];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unionn(int a,int b){
xxxx=find(a),y=find(b);
if(xxxx==y)return;
fa[xxxx]=y;
return;
}
struct edge{
int u,v,w;
}q[2000001];
int calc(int i,int j){
return (i-1)*m+j;
}
bool cmp(edge a,edge b){
return a.w>b.w;
}
int i,j;
signed main(){
int xx=0;
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>t;
int xxx,tot,u,v,a,b,c,p,cnt;
while(t--){
cin>>n>>m>>s>>s>>s>>s;
cin>>x[1]>>x[2]>>a>>b>>c>>p;
fa[1]=1;fa[2]=2;
for(i=3;i<=n*m;++i)fa[i]=i,x[i]=(a*x[i-1]+b*x[i-2]+c)%p;
for(i=1;i<=n;++i)for(j=1;j<=m;++j){
_w[i][j]=x[calc(i,j)];
}
cnt=0;
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
if(i!=n)q[++cnt]={calc(i,j),calc(i+1,j),_w[i][j]*_w[i+1][j]};
if(j!=m)q[++cnt]={calc(i,j),calc(i,j+1),_w[i][j]*_w[i][j+1]};
}
}
sort(q+1,q+cnt+1,cmp);
xxx=0,tot=0;
for(i=1;i<=cnt;++i){
u=q[i].u,v=q[i].v;
if(find(u)==find(v))continue;
unionn(u,v);
xxx+=q[i].w;
++tot;
if(tot==n*m-1)break;
}
cout<<"Case #"<<++xx<<": "<<xxx<<'\n';
}
return 0;
}
H-Tree Partition
显然你可以先二分答案,然后,你用一个动态规划,当答案没有超过二分的 \(lim\) 时就一直从小到大加子树的答案(贪心,让分的段数尽可能小),并且统计一下没有加进去的数量(就是你要断开的边),如果小于 \(k\) 二分出来的这个就是可以的!
查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,k,w[100005],dp[100005];
vector<int>nbr[100005];
int dfs(int cur,int fa,int lim){
dp[cur]=w[cur];
vector<int>v;
int res=0;
for(auto to:nbr[cur]){
if(to==fa)continue;
res+=dfs(to,cur,lim);
v.push_back(dp[to]);
}
sort(v.begin(),v.end());
int cnt=0;
for(auto i:v){
cnt++;
if(dp[cur]+i<=lim)dp[cur]+=i;
else{
res+=v.size()-cnt+1;
break;
}
}
return res;
}
bool check(int x){
return dfs(1,0,x)<k;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>t;
int cntt=0;
while(t--){
cin>>n>>k;
for(int i=1;i<=n;++i)nbr[i].clear();
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
nbr[u].push_back(v);
nbr[v].push_back(u);
}
int lt=0,rt=0;
for(int i=1;i<=n;++i)cin>>w[i],lt=max(lt,w[i]),rt+=w[i];
lt--;rt++;
while(lt+1<rt){
int mid=lt+rt>>1;
if(check(mid))rt=mid;
else lt=mid;
}
cout<<"Case #"<<++cntt<<": "<<rt<<'\n';
}
return 0;
}