集训日记:DAY7

集训日记:DAY7

盼星星盼月亮终于盼到图论,结果……爆零
这全都归功于一款名叫DEV C++的IDE(具体看后文)
集训还有3天,真的难顶……


依旧是熟悉的8:00发题,熟悉的遍历
T1……梦回字符串?这和图论有什么关系……难道是要把字母当成节点连起来???
再一看发现如果前一个字符的后两个和后一个字符的前两个相同就可以把他们连起来,然后一次枚举起点好像就可以
T2……用并查集搞一搞?但是判断一个点与集合中所有的点都认识有点难办
T3……还是并查集?按天把道路合并然后查找s,t在不在同一集合?
T4……正解没思路呀,双向边暴力bfs???
(此时过去30min)
由于四道题都有暴力思路,于是按实现难度决定先写T3毕竟并查集码量少
很顺利地20min写完调完,一测样例发现出锅了
再一看题发现思路显然不是并查集,因为i+1天i这条路就没了
此时再想新思路显然来不及,于是只好先弃掉T3
这时心态有点崩,毕竟浪费了30min
然后接着看同为并查集的T2
上去先敲了个并查集的板子,然后就蚌住了……
这个判断是真的不好办
又想之前做没做过类似的题,还是没什么思路
又试着开数组记录一下,很快又写不下去了了
正卡在那猛一抬头发现一个过去2h了
赶紧先固输了个No solution去看T1
T1又完善了一下思路
然后写的时候中间遇到了一些小问题,总体来说还算顺利
调过样例和自造数据的时候还剩1h,刚好够写完T4暴力
就在这时
我想把注释去掉,随手按下了Ctrl+z
然后
我的代码

它没了!
它变成了乱码!
我狂按Ctrl+y也回不去!

当时大脑一片空白,过了一会才意识到

是DEV C++这款亲切可爱,超级实用,装机必备的IDE吞掉了我的代码!

无奈只好重新写,这时心态反而看开了……
重写一遍调完花了30min
最后30minT4的暴力也没打完……


吃饭的时候就隐约觉得要爆零……
回来一看果然……
T2不知道为什么没有分,其他的题更不可能有分……
有趣的是T4我的暴力思路能拿到90分
也就是说

DEV C++这款亲切可爱,超级实用,装机必备的IDE在一瞬间吞掉了我90分!!!

无语子……

本次模拟赛经验:

1.证明思路正确再写题!!!


最后贴上正解和标程(我订正过的就贴了我的)

T1字符串还原

显然暴力的话 30 分是有的。
60 分这一档是我随便设的 233
我觉得应该有很多不那么靠谱但是能拿高分的构造方式,但 100 分的话需要
比较靠谱的构造技巧:
将所有长度为 3 的字符串看作从前两个字符构成的字符串所代表的节点到后
两个字符构成的字符串所代表的节点连有向边,比如:
对子串 abc,就表示节点 ab 向节点 bc 连了一条有向边。
对子串 aaa,表示节点 aa 向节点 aa 自己连了一条有向边。
构造完有向图后跑欧拉路即可,方案可以利用找到的欧拉路构造。


好家伙,显然的暴力都没拿着……
别说我还真想到字符连边了,就是欧拉路没学过不会……

#include<bits/stdc++.h>
using namespace std;
const int N=20000,M=200040;
int n,last[N]={},next[M]={},end[M]={},total_edge=0;
int in[N]={},out[N]={},path[M]={},len=0;
char ch[5]={},chset[62]={};
inline void add_edge(int u,int v)
{
	next[++total_edge]=last[u];
	last[u]=total_edge;
	end[total_edge]=v;
	++out[u];
	++in[v];
}
void init()
{
	for(int i=0;i<62;++i)
		for(int j=0;j<62;++j)
		{
			int p=chset[i]*140+chset[j];
			last[p]=in[p]=out[p]=0;
		}
	total_edge=len=0;
	scanf("%d",&n);
		assert(!feof(stdin));
	for(int i=1;i<=n;++i)
	{
		scanf("\n%s",ch);
		int u=ch[0]*140+ch[1];
		int v=ch[1]*140+ch[2];
		add_edge(u,v);
	}
}
void dfs(int s)
{
	while(last[s])
	{
		int i=last[s];
		last[s]=next[i];
		dfs(end[i]);
	}
	path[++len]=s;
}
void work()
{
	int s=0;
	for(int i=0;i<N;++i)
		if(out[i]>in[i])
		{
			if(s || out[i]-in[i]>=2)
			{
				puts("NO");
				return;
			}
			s=i;
		}
	if(s==0)
		s=ch[0]*140+ch[1];
	dfs(s);
	if(len<=total_edge)
	{
		puts("NO");
		return;
	}
	puts("YES");
	putchar(path[len]/140);
	for(int i=len;i>=1;--i)
		putchar(path[i]%140);
	putchar('\n');
}
int main()
{	
	freopen("recover.in","r",stdin);
	freopen("recover.out","w",stdout);
	for(int i=0;i<26;++i)
		chset[i]='a'+i;
	for(int i=0;i<26;++i)
		chset[i+26]='A'+i;
	for(int i=0;i<10;++i)
		chset[i+52]='0'+i;
	int T;
	scanf("%d",&T);
	while(T--)
	{
		init();
		work();
	}
	return 0;
}

T2队伍分配

将互相认识的关系图转化为补图,则相当于要对 n个人进行二染色,并且任
意一条边的两个端点颜色均不同。
因此,先判定原图是否是二分图,之后对每个连通块,设两部分大小为 s1,s2 ,
则最终分组方案中一定要包含其中的一部分,这是一个分组背包 DP 的模型,最
终检查在两部分人数均大于 0 的方案中的最优解即可,特别地,对于 n=1 的情况,
根据题意是无解的。


原来这题和并查集半毛钱关系没有……二分图+分组背包是我没想到的……

#include<bits/stdc++.h>
using namespace std;
int t,n,a[105][105],vis[105],c[105],size[105][2],tot,f[105][105];
bool dfs(int u,int col)
{
	if(vis[u]) return c[u]==col;
	vis[u]=1;c[u]=col;
	size[tot][col]++;
	for(int i=1;i<=n;i++) if(a[u][i]&&!dfs(i,!col)) return 0;
	return 1;
}
bool check()
{
	for(int i=1;i<=n;i++) 
	{
		if(!vis[i]) 
		{
			tot++;
			if(!dfs(i,0)) return 0;
		}
	}
	return 1;
}
void solve()
{
	int ans=n;
	f[0][0]=1;
	for(int i=1;i<=tot;i++)
	{
		for(int j=n;j>=0;j--)
		{
			if(j>=size[i][0]&&f[i-1][j-size[i][0]]) f[i][j]=1;
			if(j>=size[i][1]&&f[i-1][j-size[i][1]]) f[i][j]=1;
		}
	}
	for(int i=0;i<=n;i++) if(f[tot][i]) ans=min(ans,abs(n-i*2));
	cout<<ans<<endl;
}
void clear()
{
	memset(a,0,sizeof(a));
	memset(vis,0,sizeof(vis));
	memset(size,0,sizeof(size));
	memset(f,0,sizeof(f));
	memset(c,0,sizeof(c));
	tot=0;
}
int main()
{
	cin>>t;
	while(t--)
	{
		clear();
		cin>>n;
		for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>a[i][j],a[i][j]=!a[i][j];	
		tot=0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(a[i][j]||a[j][i]) a[i][j]=a[j][i]=1;
		if(n==1)
		{
			cout<<"No solution"<<endl;
			continue;
		}
		if(check()) solve();
		else cout<<"No solution"<<endl;
	}
	return 0;
}

T3旅行计划

我们考虑维护 ans[i][j],即当前从城市 i 出发,到城市 j 最早能够在什么
时候。
如果一个询问从时间 L 开始,那么我们将所有编号≥L 的边加入图中,并求
出 ans[i][j],我们就能得到该询问的答案。
很显然,如果按时间从晚到早加边,那么每条边只需要被添加一次,并且这
时当编号为 i 的边加入并更新 ans 数组后,所有 L 值为 i 的询问的结果就都知道
了,再考虑到,如果按这种顺序加边,每添加一条边(u,v)时,都只有 ans[u][…]
和 ans[v][…]可能改变――因为现在从这两个点出发时多了一个选择——可以
在时间 L 走向对方,之后的时间从对方的点出发,而其它城市的答案都不会改变,
因为从它们出发都无法经过这一条新的边,只能走已有的边。
显然,加完边后,u,v 两城市到达某一其它城市 t 的最早时间改变为
了 min(ans[u][t],ans[v][t]),直接维护即可。
为了实现上述算法,我们需要先读入所有的询问,在从晚到早添加编号为 i
的边并更新了 ans 数组之后,回答所有 L 值为 i 的询问,整个算法总时间
复杂度 O(nm+qlogq),维护过程常数很小,2s 时限足以通过。


不会……无语子

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=200200,Q=200200,Inf=1000000;
struct query
{
	int l,r,s,t,num;
}a[Q];
bool cmp(const query &q1,const query &q2) {return q1.l>q2.l;}
int n,m,q,u[M]={},v[M]={},dis[N][N]={},ans[Q]={};
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;++i) scanf("%d%d",u+i,v+i);
	for(int i=1;i<=q;++i)
	{
		scanf("%d%d%d%d",&a[i].l,&a[i].r,&a[i].s,&a[i].t);
		a[i].num=i;
	}
	sort(a+1,a+q+1,cmp);
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dis[i][j]=Inf;
	int p=1;
	for(int i=m;i>=1;--i)
	{
		dis[u[i]][v[i]]=dis[v[i]][u[i]]=i;
		for(int j=1;j<=n;++j) dis[u[i]][j]=dis[v[i]][j]=min(dis[u[i]][j],dis[v[i]][j]);
		while(p<=q && a[p].l==i)
		{
			ans[a[p].num]=(dis[a[p].s][a[p].t]<=a[p].r);
			++p;
		}
	}
	for(int i=1;i<=q;++i) puts(ans[i] ? "Yes" : "No");
	return 0;
}

T4路径子序列

显然,我们只要考虑图中出现在了给定路径上的点即可,不妨假设图中所有
的点都在给定路径上。
对于任意一条连接给定路径上两个点(不一定相邻)的无向边,我们根据这
两个点在路径中的先后次序给无向边定向(先出现在路径中的->后出现在路径中
的),这样,任意一条从 1 到 n 的路径都是原路径的路径子序列,并且原路径的
任意一个路径子序列都能对应图中一条 1 到 n 的路径。
因此,我们只要在定向后的图中 BFS 一遍即可求出答案。


我的90分啊啊啊!!!

#include<bits/stdc++.h>
using namespace std;
int t,n,m,k,a[50005],ans,cnt,vis[50005];
struct edge
{
	int nxt,to;
}e[200005];
int head[200005],ecnt;
void add(int x,int y)
{
	e[++ecnt]=(edge){head[x],y};
	head[x]=ecnt;
}
int dis[50005],vis1[50005];
void dij()
{
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	priority_queue<pair<int,int> >q;
	q.push(make_pair(0,1));
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if(vis1[u]) continue;
		vis1[u]=1;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(dis[v]<=dis[u]+1) continue;
			dis[v]=dis[u]+1;
			q.push(make_pair(-dis[v],v));
		}
	}
}
void clear()
{
	memset(vis,0,sizeof(vis));
	memset(head,0,sizeof(head));
	memset(vis1,0,sizeof(vis1));
	ecnt=0;
}
int main()
{
	cin>>t;
	while(t--)
	{
		clear();
		cin>>n>>m>>k;
		for(int i=1;i<=k+1;i++) cin>>a[i];
		for(int i=1;i<=k;i++)
		{
			vis[a[i]]=++cnt;
			vis[a[i+1]]=++cnt;
			add(a[i],a[i+1]);
		}
		for(int i=1;i<=m-k;i++)
		{
			int u,v;
			cin>>u>>v;
			if(vis[u]&&vis[v]) vis[u]>vis[v]?add(v,u):add(u,v);
		}
		dij();
		cout<<dis[n]<<endl;
	}
	return 0;
}
/*
1
7 8 5  
1 2 3 4 5 7
1 3
3 6
6 7
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值