2019.10.4 noip模拟赛

T1 三角

【题目描述】
ZGY 有一个三角,就像下面这样(每一个点都有一个权值)
tri
第 1 层有 1 个,第 2 层有 2 个,第 i 层有 i 个。

这个三角一共有 n 层,ZGY 每次可以从第 i 层的第 j 个走到第 i + 1 层的第 j 个或是第 j + 1 个,直到走到第 n 层。从第 1 层走到第 n 层的一种方案成为一条路径,路径的权值为路径 上点权值之和。

现在 ZGY 想知道,权值前 k 大的路径(存在多个正确答案)。

【输入格式】
第一行,两个整数 n, k 表示 三角一共有 n 层,ZGY 想知道权值前 k 大的路径。

接下来 n 行: 其中第 i 行包含 i 个整数,其中第 j 个整数 Wij表示 第 i 层第 j 个点权值为 Wij

【输出格式】
输出数据包含 k 行,每行表示一条路径包含一个由“L” 和 “R”组成的字符串,长 度为 n - 1 其中第 i 个字符表示在第 i 层时向下一层走的方向。

假设当前在第 i 行第 j 个点,如果为“L”则走向第 i + 1 行第 j 个点,如果为“R”则走向第 i + 1 行第 j + 1 个点.

解析

因为我们可以确定权值总和最大的方案,所以可以考虑二分答案。

先预处理,从下往上dp出最大的方案。二分答案,二分一个mid,深搜。因为直接深搜的复杂度太高,所以要剪枝。若在当前节点的权值加上从当前节点到底最大的值仍小于mid,直接返回。如果统计到底了,记录方案数。若方案数大于等于k,l=mid+1;反之r=mid。

最后由答案再dfs一遍,输出答案即可。

细节

注意二分答案的细节,我被虐吐了。

代码

#include<cstdio>
#include<algorithm>
#define maxn 1005
using namespace std;

int n,k,sum,cnt,fl,nw;
int val[maxn][maxn];
int ans[maxn],dp[maxn][maxn];

void dfs(int x,int y,int sum,int lim)
{
	if(nw>=k)
	{
		fl=1;
		return;
	}
	if(sum+dp[x][y]-val[x][y]<lim)return;
	if(x==n)
	{
		nw++;
		return;
	}
	dfs(x+1,y,sum+val[x+1][y],lim);
	if(fl)return;
	dfs(x+1,y+1,sum+val[x+1][y+1],lim);
	if(fl)return;
}//判断有无解

int check(int x)
{
	fl=0;
	nw=0;
	dfs(1,1,val[1][1],x);
	if(fl)return 1;
	return 0;
}

void solve(int x,int y,int sum,int lim)
{
	if(sum+dp[x][y]-val[x][y]<lim)return;
	if(x==n)
	{
		for(int i=1;i<n;++i)
		{
			if(ans[i]==0)printf("L");
			else printf("R");
		}
		printf("\n");
		nw++;
		return;
	}
	ans[x]=0;
	solve(x+1,y,sum+val[x+1][y],lim);
	if(nw==k)return;
	ans[x]=1;
	solve(x+1,y+1,sum+val[x+1][y+1],lim);
	if(nw==k)return;
}

int main()
{
	freopen("tri.in","r",stdin);
	freopen("tri.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=i;++j)
		{
			scanf("%d",&val[i][j]);
			sum+=val[i][j];
		}
	}
	for(int i=1;i<=n;++i)
	dp[n][i]=val[n][i];
	for(int i=n-1;i>=1;--i)
	for(int j=1;j<=i;++j)
	dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+val[i][j];
	int l=0,r=dp[1][1],mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid))l=mid+1;
		else
		r=mid;
	}
	nw=0;
	solve(1,1,val[1][1],mid);
	return 0;
}

总结

别忘了基础算法 Q A Q QAQ QAQ

T2 C++锦标赛

【题目描述】
ZGY 报名参加了 C++锦标赛,在这个比赛中前 K 名可以获得一本《C++ Primer》,当然 ZGY 也想要一本。

这个比赛已经有 n 个人参加,并且互相之间已经进行了比赛,每一个人都有一个得分, 用于最后排名。
ZGY 作为第 n + 1 个参赛者,需要与之前的每一个人打一场比赛,当然 ZGY 最开始的得分等于 0。

对于每场比赛,有两个人参加,不会存在平局,胜者得分增加 1,败者得分不变。最后 按照得分从高到低来排名,假设有人与ZGY最终得分相同,那么如果那个人曾经输给了ZGY 就会排在 ZGY 后面,否则会排在 ZGY 的前面。

如果 ZGY 赢了某个人,就需要消耗相应的 RP 值,现在 ZGY 可以决定赢那些人,所以 ZGY 想知道最少需要消耗多少 RP 值才能够获得一本《C++ Primer》。

【输入格式】
本题包含多组数据,第一行读入一个整数 T 表示数据组数

对于每一组数据 第一行,包含两个整数 n, k 表示在 ZGY 之前已经有 n 个人参加比赛,前 k 名可以 获得《C++ Primer》。

接下来 n 行,每行包含两个整数 pi和 ei表示第 i 个人已经获得的得分和赢第 i 个人 所需要花费的 RP(注意在 ZGY 来之前这 n 个人的比赛得分不一定满足上述计分规则)

【输出格式】
对于每一组数据,输出一个整数,表示最少需要花费的 RP,如果无论如何也无法进入 前 K 名,则输出-1

解析

题目当中有这句话应当引起重视:

假设有人与ZGY最终得分相同,那么如果那个人曾经输给了ZGY 就会排在 ZGY 后面,否则会排在 ZGY 的前面

说明我们在做题时一定要考虑最后与ZGY得分相同的人。

还有一个细节:

胜者得分增加 1,败者得分不变

也就是说,当对手赢了之后,对手的得分也要加一。

说明我们要分类讨论(我也不知道这是怎么想出来的)。先把对手按照得分从大到小排序,确定ZGY的最小得分,设为 a i m aim aim,以及ZGY至少要超过多少个与最小得分相同的人数后才能刚好到第k名。

下面开始分类讨论:

case1
当ZGY的得分为 a i m + 2 aim+2 aim+2时。因为若ZGY输给了所有得分为 a i m aim aim的人,他的得分依旧在第k名内。所以直接从小到大枚举 a i m + 2 aim+2 aim+2 r p rp rp值,加起来即可。

ll work1()//得分为aim+2分,无忧无虑
{
	ll ret=0;
	for(ll i=1;i<=aim+2&&i<=n;++i)
	ret+=tr[i].e;
	return ret;
}

case2
当ZGY的得分为 a i m + 1 aim+1 aim+1时,这是若ZGY输给了得分为 a i m aim aim a i m + 1 aim+1 aim+1的人,他的地位可能不保,因为 a i m aim aim的得分会变成 a i m + 1 aim+1 aim+1。假设有 a a a个人的得分大于等于 a i m aim aim,设 m o r e = a − k more=a-k more=ak,那么ZGY只需要超过 m o r e more more个得分为 a i m aim aim或者 a i m + 1 aim+1 aim+1的人就可以保住他的名次了。不好解释,想想这是为什么?

所以我们先处理掉这 m o r e more more个分数为 a i m aim aim a i m + 1 aim+1 aim+1的人,再在剩下的人中找 a i m + 1 − m o r e aim+1-more aim+1more个人。当然, r p rp rp值还是要从小到大。

ll work2()//得分为aim+1分,要超过至少more个aim和aim+1的人
{
	memset(vis,0,sizeof(vis));
	ll qwq=more,tql=aim+1,ret=0;
	for(ll i=1;i<=n&&qwq;++i)
	{
		if(tr[i].p==aim||tr[i].p==aim+1)
		{
			ret+=tr[i].e;
			qwq--;
			tql--;
			vis[i]=1;
		}
	}
	for(ll i=1;i<=n&&tql>0;++i)
	{
		if(vis[i])continue;
		ret+=tr[i].e;
		tql--;
	}
	return ret;
}

case3
当ZGY的得分刚好为 a i m aim aim时,这时只用考虑得分为 a i m − 1 aim-1 aim1 a i m aim aim的人就可以了。设得分为 a i m − 1 aim-1 aim1的人有 t q r tqr tqr个(日膜 t q r tqr tqr),很明显,ZGY要超过 t q r tqr tqr个得分为 a i m − 1 aim-1 aim1 a i m aim aim的人,同时还要超过 m o r e more more个得分为 a i m aim aim的人,才能保证他的地位不被动摇。最后在找剩下的 a i m − m o r e − t q r aim-more-tqr aimmoretqr个人就可以了。

ll work3()//得分为aim分,要超过more个aim分和aim-1分个aim-1分或aim分的人
{
	memset(vis,0,sizeof(vis));
	ll qwq=more,tqr=0,tql=aim,ret=0;
	for(ll i=1;i<=n;++i)
	{
		if(tr[i].p==aim-1)
		tqr++;
	}//记录aim-1分的人
	for(ll i=1;i<=n&&qwq;++i)
	{
		if(tr[i].p==aim)
		{
			qwq--;
			tql--;
			ret+=tr[i].e;
			vis[i]=1;
		}
	}//超过more个aim的人
	for(ll i=1;i<=n&&tqr;++i)
	{
		if((tr[i].p==aim||tr[i].p==aim-1)&&(!vis[i]))
		{
			tqr--;
			tql--;
			ret+=tr[i].e;
			vis[i]=1;
		}
	}//超过tqr个aim-1或aim分的人
	for(ll i=1;i<=n&&tql>0;++i)
	{
		if(!vis[i])
		{
			ret+=tr[i].e;
			tql--;
		}
	}
	return ret;
}

当ZGY的得分小于 a i m aim aim时,显然不合题意啊。最后三个答案取最小值,即为最终结果。

代码

#include<cstdio>
#include<cstring>
#include<assert.h>
#include<algorithm>
#define ll long long
#define maxn 300005
using namespace std;

struct node
{
	ll e,p;
}tr[maxn];
ll t,n,k,ans;
ll aim,more;//目标分数,多的人数
ll vis[maxn];

bool cmp1(node x,node y)
{
	return x.p>y.p;
}

bool cmp2(node x,node y)
{
	return x.e<y.e;
}

ll judge()
{
	ll tot=0;
	for(ll i=1;i<=n;++i)
	if(tr[i].p>n)tot++;
	if(tot>k-1)return 1;
	return 0;
}

ll work1()//得分为aim+2分,无忧无虑
{
	ll ret=0;
	for(ll i=1;i<=aim+2&&i<=n;++i)
	ret+=tr[i].e;
	return ret;
}

ll work2()//得分为aim+1分,要超过至少more个aim和aim+1的人
{
	memset(vis,0,sizeof(vis));
	ll qwq=more,tql=aim+1,ret=0;
	for(ll i=1;i<=n&&qwq;++i)
	{
		if(tr[i].p==aim||tr[i].p==aim+1)
		{
			ret+=tr[i].e;
			qwq--;
			tql--;
			vis[i]=1;
		}
	}
	for(ll i=1;i<=n&&tql>0;++i)
	{
		if(vis[i])continue;
		ret+=tr[i].e;
		tql--;
	}
	return ret;
}

ll work3()//得分为aim分,要超过more个aim分和aim-1分个aim-1分或aim分的人
{
	memset(vis,0,sizeof(vis));
	ll qwq=more,tqr=0,tql=aim,ret=0;
	for(ll i=1;i<=n;++i)
	{
		if(tr[i].p==aim-1)
		tqr++;
	}//记录aim-1分的人
	for(ll i=1;i<=n&&qwq;++i)
	{
		if(tr[i].p==aim)
		{
			qwq--;
			tql--;
			ret+=tr[i].e;
			vis[i]=1;
		}
	}//超过more个aim的人
	for(ll i=1;i<=n&&tqr;++i)
	{
		if((tr[i].p==aim||tr[i].p==aim-1)&&(!vis[i]))
		{
			tqr--;
			tql--;
			ret+=tr[i].e;
			vis[i]=1;
		}
	}//超过tqr个aim-1或aim分的人
	for(ll i=1;i<=n&&tql>0;++i)
	{
		if(!vis[i])
		{
			ret+=tr[i].e;
			tql--;
		}
	}
	return ret;
}

int main()
{
	freopen("tournament.in","r",stdin);
	freopen("tournament.out","w",stdout);
	scanf("%lld",&t);
	while(t--)
	{
		memset(vis,0,sizeof(vis));
		ans=0;
		scanf("%lld%lld",&n,&k);
		for(ll i=1;i<=n;++i)
		scanf("%lld%lld",&tr[i].p,&tr[i].e);
		if(k==n+1)
		{
			printf("0\n");
			continue;
		}
		if(judge())
		{
			printf("-1\n");
			continue;
		}
		sort(tr+1,tr+1+n,cmp1);//按得分排序
		aim=tr[k].p;
		more=1;
		while(tr[k+more].p==tr[k].p)more++;
		sort(tr+1,tr+1+n,cmp2);//按rp值排序
		ans=min(min(work1(),work2()),work3());//分类贪心
		printf("%lld\n",ans);
	}
	return 0;
}

总结

这道题思维难度有点高,能想到分类就是本题的突破口。但是怎么想到分类还需多加练习。

T3 ZGY的早餐

【题目描述】
ZGY 每天早上要从宿舍走路到机房,顺便从学校小卖部购买早饭,当然机智的 ZGY 一 定会走最短路。

学校的路可以看成一无向联通张图,图上有 n 个点,m 条边,每一个点都有一个唯一的 编号 1~n,每一条边有一个边权,表示两个点之间的距离,ZGY 的宿舍在 S 点,机房在 T 点,而小卖部在 H 点。

现在 ZGY 想知道从宿舍经过小卖部到达机房的最短距离,不过因为在这个世界上有 Q 个 ZGY,所以你必须回答 Q 个问题。

【输入格式】
第一行包含三个正整数 T, n , m 表示这是第 T 个数据图上有 n 个点 m 条边

接下来 m 行, 每行有三个整数 u, v, w 表示点 u 与 v 之间有一条长度为 w 的边(题 目保证不存在自环、重边)

接下来一行,包含一个整数 Q 表示 ZGY 的个数 接下来 Q 行,每行三个整数 S, H, T 分别表示宿舍、小卖部、机房所在的点的编号

【输出格式】
对于每一个询问 Q,输出一行,表示对于这个询问的答

ZGY

解析

这道题我特地贴出来了数据规模,说明数据规模很重要。

乍一看第8,9,10个数据点的 n ≤ 1 0 5 n\leq10^5 n105,就发现没有最短路算法能胜任。但仔细看右边的其他,图连通无环,不就是棵树吗???好一个文字游戏,然而我还是被骗到了。

所以本题的算法:数据分治

对于数据点1~5,直接用Floyed求解。

对于数据点6~10,用类似于倍增求lca的算法,求出S到H的距离,再求出H到T的距离,加起来就可以了。 这是这次考试最水的题

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 1005
#define maxm 250005
using namespace std;

struct node
{
	ll v,next;
	ll val;
}tr[maxm<<1];
ll t,n,m,q;
ll dis[maxn][maxn];
ll tot,head[maxm];
ll dep[maxm],f[maxm][25],sum[maxm][25];

void add(ll x,ll y,ll z)
{
	tot++;
	tr[tot].v=y;
	tr[tot].val=z;
	tr[tot].next=head[x];
	head[x]=tot;
}

void floyed()
{
	for(ll i=1;i<=n;++i)
	dis[i][i]=0;
	for(ll k=1;k<=n;++k)
	{
		for(ll i=1;i<=n;++i)
		{
			for(ll j=1;j<=n;++j)
			{
				if(dis[i][j]>dis[i][k]+dis[k][j])
				dis[i][j]=dis[i][k]+dis[k][j];
			}
		}
	}
}

void ready(ll x,ll fa)
{
	dep[x]=dep[fa]+1;
	for(ll i=0;i<=20;++i)
	{
		f[x][i+1]=f[f[x][i]][i];
		sum[x][i+1]=sum[x][i]+sum[f[x][i]][i];
	}
	for(ll t=head[x];t;t=tr[t].next)
	{
		ll y=tr[t].v,z=tr[t].val;
		if(y==fa)continue;
		f[y][0]=x;
		sum[y][0]=z;
		ready(y,x);
	}
}

ll lca(ll x,ll y)
{
	ll ret=0;
	if(dep[x]<dep[y])swap(x,y);
	for(ll i=20;i>=0;--i)
	{
		if(dep[f[x][i]]>=dep[y])
		{
			ret+=sum[x][i];
			x=f[x][i];
		}
		if(x==y)return ret;
	}
	for(ll i=20;i>=0;--i)
	{
		if(f[x][i]!=f[y][i])
		{
			ret+=sum[x][i]+sum[y][i];
			x=f[x][i];
			y=f[y][i];
		}
	}
	ret+=sum[x][0]+sum[y][0];
	return ret;
}

void work1()
{
	for(ll i=1;i<=m;++i)
	{
		ll x,y;
		ll z;
		scanf("%lld%lld%lld",&x,&y,&z);
		dis[x][y]=min(dis[x][y],z);
		dis[y][x]=min(dis[y][x],z);
		add(x,y,z);
		add(y,x,z);
	}
	floyed();
	scanf("%lld",&q);
	for(ll i=1;i<=q;++i)
	{
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		printf("%lld\n",dis[x][y]+dis[y][z]);
	}
}

void work2()
{
	for(ll i=1;i<=m;++i)
	{
		ll x,y;
		ll z;
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	ready(1,0);
	scanf("%lld",&q);
	for(ll i=1;i<=q;++i)
	{
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		printf("%lld\n",lca(x,y)+lca(y,z));
	}
}

int main()
{
	freopen("mindis.in","r",stdin);
	freopen("mindis.out","w",stdout);
	memset(dis,0x3f3f3f3f,sizeof(dis));
	scanf("%lld%lld%lld",&t,&n,&m);
	if(t<=6)
	work1();//前6个点,floyed
	else
	work2();
	return 0;
}

总结

认真读题(雾

最后,这次考试的算法范围应该都在noip的难度以内,但是思维难度较大。所以平常还是要多接触题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值