2022.01.08 模拟赛

鉴于我渣渣的动态规划,我决定先写第一题与第三题。

第一题 最短路+二分

每次二分让所有有向边增加的边权,但是不仅可以增加,还可以减小,所以 \(L=-1e5,R=1e5\) ,每次二分完跑最短路,这里要跑SPFA,万一有负权环还跑Dijkstra就挂了。跑的时候不挺给每条边的边权增加二分的时间就行。

期望得分100,但是我挂了,只有20。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const int N=110;
const int M=N*N;
const int inf=0x3f3f3f3f;
int n,m,T,cnt,head[N],dis[N],vis[N],cnti[N];
struct node{
	int to,next,val;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void add(int u,int v,int w){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline int spfa(int s,int t,int addi){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(cnti,0,sizeof(cnti));
	queue<int>q;
	vis[s]=cnti[s]=1;
	dis[s]=0;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		++cnti[x];
		if(cnti[x]>n)return 0;
		for(int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].val+addi){
				dis[v]=dis[x]+a[i].val+addi;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]>=0;
}

signed main(){
	freopen("tstrip.in","r",stdin);
	freopen("tstrip.out","w",stdout);
	T=read();
	while(T--){
		cnt=0;
		memset(head,0,sizeof(head));
		n=read();m=read();
		for(int i=1;i<=m;i++){
			int u,v,w;
			u=read();v=read();w=read();
			add(u,v,w);
		}
		int flag=spfa(1,n,0);
		if(dis[n]==inf){
			puts("-1");
			continue;
		}
		int L=-1e5+10,R=1e5+10,mid,ans=0x3f3f3f3f;
		while(L<R){
			mid=(L+R)>>1;
			if(spfa(1,n,mid))R=mid,ans=min(ans,dis[n]);
			else L=mid+1;
		}
		cout<<ans<<endl;
	}
	return 0;
}
/*
1
4 4
1 2 1
1 3 1
2 3 -3
3 1 1
ans -1
*/

第三题 最短路+最小割

因为每两个节点之间最多只有一条直接连接的边,所以不会有重边的情况发生。神兽每次选择走最短的路径,说明我们必须把从1到 \(n\) 之间的最短道路全部增加,所以我们要记录一下最短道路到底是哪些。

这里介绍一种极其简单的方法,这个方法还被我用在很多奇奇怪怪的字符串题中。

from[i][j] 表示到 i 这个节点并且所走的路径是最短的那条的无向边的另一个端点。那么我们可以用 from[i][0] 表示一共有几个这样的端点,每次增加新的端点增加 from[i][0] ,把这个端点存在 from[i][from[i][0]] 中。

好了,把只有最短路径的图重新拎出来,让后我们需要找在某些边上放一只小Xie的边数最小值。至于为啥放一只小Xie——只增加一时间不就增长了嘛,这和增加两只小Xie的结果是一样的。这个问题可以继续转化为如何选中某些边让路径上其他的节点分属两个不同的阵营。这样一来,只需要跑个最小割就能解决啦~

预期得分100~

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const int N=1010;
const int M=499500*2+10;
const int inf=0x3f3f3f3f;
int n,m,S,T,cnt,tot=1,head[N],headi[N],vis[N],dis[N],cur[N],dep[N],from[N][N];
struct node{
	int to,next,val;
}a[M],e[M];
struct nodei{
	int pos,dis;
	bool operator <(const nodei &b)const{
		return dis>b.dis;
	}
};

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void add(int u,int v,int w){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline void dijkstra(int s,int t){
	memset(dis,inf,sizeof(dis));
	dis[s]=0;
	priority_queue<nodei>q;
	q.push({s,0});
	while(!q.empty()){
		nodei tmp=q.top();q.pop();
		int x=tmp.pos;
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].val){
				dis[v]=dis[x]+a[i].val;
				from[v][0]=1;from[v][from[v][0]]=x;
				if(!vis[v])q.push({v,dis[v]});
			}else if(dis[v]==dis[x]+a[i].val){
				from[v][0]+=1,from[v][from[v][0]]=x;
			}
		}
	}
}
inline void addi(int u,int v,int w){
	++tot;
	e[tot].to=v;
	e[tot].val=w;
	e[tot].next=headi[u];
	headi[u]=tot;
}
inline void color(){
	//cout<<"shortest "<<endl;
	deque<int>q;
	q.push_back(T);vis[T]=1;
	while(!q.empty()){
		int x=q.front();q.pop_front();
		for(int i=1;i<=from[x][0];i++){
			int u=from[x][i];
			if(!vis[u])vis[u]=1,q.push_back(u);
			addi(u,x,1),addi(x,u,0);
			//cout<<u<<" "<<x<<endl;
		}
	}
}
inline int bfs(int s,int t){
	memcpy(cur,headi,sizeof(headi));
	memset(dep,0,sizeof(dep));
	queue<int>q;
	q.push(s);dep[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=headi[x];i;i=e[i].next){
			int v=e[i].to;
			if(!dep[v]&&e[i].val){
				dep[v]=dep[x]+1;
				q.push(v);
			}
		}
	}
	return dep[t];
}
inline int dfs(int x,int t,int f){
	if(x==t)return f;
	int ans=0;
	for(int i=cur[x];i&&f>ans;i=e[i].next){
		cur[x]=i;
		int v=e[i].to,fi=0;
		if(dep[v]==dep[x]+1&&e[i].val&&(fi=dfs(v,t,min(f-ans,e[i].val)))>0)
		e[i].val-=fi,e[i^1].val+=fi,ans+=fi;
	}
	return ans;
}
inline int Dinic(int s,int t){
	int flow=0;
	while(bfs(s,t)){
		int x=0;
		if((x=dfs(s,t,inf))>0)flow+=x;
		//cout<<x<<endl;//
	}
	return flow;
}

signed main(){
	freopen("greendam.in","r",stdin);
	freopen("greendam.out","w",stdout);
	n=read();m=read();S=read();T=read();
	for(int i=1;i<=m;i++){
		int u,v,w;
		u=read();v=read();w=read();
		add(u,v,w);add(v,u,w);
	}
	dijkstra(S,T);
	//cout<<"dis "<<endl;
	//for(int i=1;i<=n;i++)cout<<dis[i]<<" ";cout<<endl;
	memset(vis,0,sizeof(vis));
	color();
	cout<<Dinic(S,T);
	return 0;
}

第二题 区间DP

这道题长着一张标准的区间DP的脸……但是我挂了。

f[i][j][k] 为从 ij 答案为 k 既加又乘的符号的个数,g[i][j][k] 是只加,h[i][j][k] 是只乘。

我们只需要先算一遍只加的符号个数,再算一遍只乘的符号个数,最后两个混合算一下就行~

预期得分100,实际得分0。我挂了,就是如此真实。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const int N=210;
const int M=25;
const int inf=0x3f3f3f3f;
int n,t,a[M],f[M][M][N],g[M][M][N],h[M][M][N];
char s[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}

signed main(){
	//freopen("puzzle.in","r",stdin);
	//freopen("puzzle.out","w",stdout);
	while(~scanf("%s%d",s,&t)&&t>=0){
		memset(a,0,sizeof(a));
		memset(f,inf,sizeof(f));
		memset(g,inf,sizeof(g));
		memset(h,inf,sizeof(h));
		n=strlen(s);
		for(int i=1;i<=n;i++){
			a[i]=s[i-1]-'0';
			f[i][i][a[i]]=g[i][i][a[i]]=h[i][i][a[i]]=0;
		}
		for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++){
			int x=0;
			for(int k=i;k<=j;k++)x=x*10+a[i];
			f[i][j][x]=g[i][j][x]=h[i][j][x]=0;
		}
		for(int len=2;len<=n;len++)
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				for(int x=0;x<=t;x++)if(g[i][k][x]!=inf)
				for(int y=0;y<=t;y++)if(g[k+1][j][y]!=inf)
				if(x+y<=t)g[i][j][x+y]=min(g[i][j][x+y],g[i][k][x]+g[k+1][j][y]+1);
				for(int x=0;x<=t;x++)if(h[i][k][x]!=inf)
				for(int y=0;y<=t;y++)if(h[k+1][j][y]!=inf)
				if(x*y<=t)h[i][j][x*y]=min(h[i][j][x*y],h[i][k][x]+h[k+1][j][y]+1);
			}
		}
		for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
		for(int k=0;k<=t;k++){
			if(g[i][j][k]!=inf)f[i][j][k]=min(f[i][j][k],g[i][j][k]);
			if(h[i][j][k]!=inf)f[i][j][k]=min(f[i][j][k],h[i][j][k]);
		}
		for(int len=2;len<=n;len++)
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++)
			for(int x=0;x<=t;x++)
			for(int y=0;y<=t;y++){
				if(g[i][k][x]!=inf&&g[k+1][j][y]!=inf&&x+y<=t)
				f[i][j][x+y]=min(f[i][j][x+y],g[i][k][x]+g[k+1][j][y]+1);
				if(g[i][k][x]!=inf&&h[k+1][j][y]!=inf&&x+y<=t)
				f[i][j][x+y]=min(f[i][j][x+y],g[i][k][x]+h[k+1][j][y]+1);
				if(h[i][k][x]!=inf&&g[k+1][j][y]!=inf&&x+y<=t)
				f[i][j][x+y]=min(f[i][j][x+y],h[i][k][x]+g[k+1][j][y]+1);
				if(h[i][k][x]!=inf&&h[k+1][j][y]!=inf&&x+y<=t)
				f[i][j][x+y]=min(f[i][j][x+y],h[i][k][x]+h[k+1][j][y]+1);
			}
		}
		if(f[1][n][t]==inf)puts("-1");
		else cout<<f[1][n][t]<<endl;
	}
	return 0;
}
 posted on 2022-01-08 15:52  eleveni  阅读(29)  评论(0)    收藏  举报