T1 最小生成樹
題面:給你一個無向帶權連通圖,每條邊是黑色或白色。讓你求一棵恰好有 \(need\) 條白色邊的
權值和最小的生成樹。題目保證有解。
題解:
最小生成樹,自然而然想到kruskal演算法,但我們要讓白點恰好有 \(need\) 條,在白點多的時候,要多選黑點,在白點少的時候,要多選白點,所以我們可以根據白點選擇情況對黑點的權值進行調整,在白點多的時候,減少黑點權值,在白點少的時候,增加黑點權值,然後發現隨著黑點權值增加量的增加,白點數量單調遞增,所以考慮二分。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,k,fa[N],ans,need,sum,cnt;
struct node
{
int u,v,w,cl;
}bi[N];
int find(int x)
{
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
bool cmp(node x,node y)
{
if(x.w!=y.w)return x.w<y.w;
else return x.cl<y.cl;
}
void kus()
{
sum=0,cnt=0,need=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int f1=find(bi[i].u),f2=find(bi[i].v);
if(f1==f2)continue;
cnt++;
if(bi[cnt].cl==0)need++;
fa[f2]=f1;
sum+=bi[i].w;
if(cnt==n-1)return;
}
}
int main()
{
freopen("e.in","r",stdin);
freopen("e.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
cin>>bi[i].u>>bi[i].v>>bi[i].w>>bi[i].cl;
int l=-1005,r=1005;
while(l<r)
{
int mid=l+(r-l)/2;
for(int i=1;i<=m;i++)if(bi[i].cl==0)bi[i].w+=mid;
sort(bi+1,bi+m+1,cmp);
kus();
for(int i=1;i<=m;i++)if(bi[i].cl==0)bi[i].w-=mid;
if(need>=k)ans=sum-mid*k,l=mid+1;
else r=mid;
}
cout<<ans<<endl;
return 0;
}
反思:做過這道題,甚至不止一次,但考場上沒做出來,還是自己學的不太好。
T2 矩陣遊戲
反向思考,一個正對角線上都有黑點,每個黑點都來自原圖的某一行某一列,因為交換操作,同一列同一行的點始終在同一列同一行。所以原圖的這一行和這一列繫結起來貢獻一個點在對角線上,所以對每個黑點行列連邊。跑二分匹配圖,有完美匹配就有解,否則無解。
#include <bits/stdc++.h>
using namespace std;
const int M=4e4+10,N=4e4+10;
int t,n,tot,nxt[M],go[M],hd[N],girl[N],ans;
bool vis[N];
void add(int x,int y)
{
nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
return ;
}
bool find(int x)
{
for(int i=hd[x];i;i=nxt[i])
{
int v=go[i];
if(vis[v])continue;
vis[v]=1;
if(!girl[v]||find(girl[v]))
{
girl[v]=x;
return 1;
}
}
return 0;
}
int main()
{
freopen("f.in","r",stdin);
freopen("f.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
tot=0,ans=0;
memset(hd,0,sizeof(hd));
memset(girl,0,sizeof(girl));
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;cin>>x;
if(x==1)add(i+n,j);
}
for(int i=1+n;i<=2*n;i++)
{
ans+=find(i);
for(int i=1;i<=n;i++)vis[i]=0;
}
if(ans==n)cout<<"Yes"<<'\n';
else cout<<"No"<<'\n';
}
return 0;
}
反思:想到了做法,同時寫出了程式,但因為一開始遇到不符合條件就輸出No,所以有地方沒清零,掛了20分,以後每次輸出清零後的陣列檢查。
T3 貪吃蛇
爆搜加一種alpha-beta剪枝,直接做。
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 25
#define CT 51
#define inf 0x3f3f3f3f
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
int grid[maxn][maxn],n,m;
int dfs(int rnd,int player,int alpha,int beta,int fx,int fy,int px,int py)
{
int flag=0,ans;
if(player==1)ans=beta;
else ans=alpha;
for(int i=0;i<4;i++)
{
int x=fx+dx[i],y=fy+dy[i];
if(x&&y&&x<=n&&y<=m&&!grid[x][y])
{
flag=1;
int cur;
grid[x][y]=player;
if(player==1)cur=dfs(rnd+1,2,alpha,ans,px,py,x,y);
else cur=dfs(rnd+1,1,ans,beta,px,py,x,y);
grid[x][y]=0;
if(player==2&&cur<=beta)return beta;
if(player==1&&cur>=alpha)return alpha;
if(player==1)ans=max(ans,cur);
else ans=min(ans,cur);
}
}
if(!flag)
{
if(player==1)return -CT+rnd;
else return CT-rnd;//提示:如果自己能贏,一定會盡量快地贏;如果自己會輸,一定會死得儘量晚.
}
return ans;
}
int main()
{
freopen("h.in","r",stdin);
freopen("h.out","w",stdout);
scanf("%d%d",&n,&m);
int x1,y1,x2,y2;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
scanf("%d",&grid[i][j]);
if(grid[i][j]==1) x1=i,y1=j;
if(grid[i][j]==2) x2=i,y2=j;
}
int ans=dfs(1,1,inf,-inf,x1,y1,x2,y2);
if(ans<0) printf("2 %d\n",ans+CT);
else printf("1 %d\n",CT-ans);
return 0;
}
反思:至少應該把爆搜寫出來。
T4 資訊攔截
挖坑。