Codeforces #38H: The Great Marathon 题解

非常好的题目,考验思维和代码技巧

这道题目有一个性质,答案只关心每个人拿了什么奖牌,不关心他们的最终排名和最终成绩

所以要想让一个人拿金牌,那么他就应该在1~n中选最短路径最短的点作为终点,反之,要想让一个人拿铜牌,就应该选1~n中最短路径最长的点作为终点

于是我们可以获得n个最短点和n个最长点,我们可以确定,最终的金牌成绩线和铜牌成绩线一定在这些数中产生

所以我们可以枚举金牌线和铜牌线,O(n^2)

对于当前的线,G[i]表示第i个人可不可能拿到金牌,S[i]表示第i个人可不可能拿到银牌,B[i]表示第i个人可不可能拿到铜牌

G[i]只要判断最短时间是否比金牌线短

B[i]只要判断最长时间是否比铜牌线长

S[i]要判断是否存在一个时间在金牌线和铜牌线之间

然后考虑dp[i][j][k]为考虑到第i个人,当前有j个金牌和k个银牌的方案数,很好转移

总复杂度O(n^5)


有若干个注意点:

1.题目中提到如果dist相同则按照选手的编号从小到大排序

这里有一个trick: dist[i][j]=dist[i][j]*n+i

因为编号都不大于n,所以这个变换不会破坏原来不同的数之间的顺序,相同的数则会根据i的大小排序

这样枚举金牌线和铜牌线就方便了

2.这个dp状态是存在重复计数的,事实上,当金牌线为i,铜牌线为j的时候,有大量的方案是没有选手压线的,这样就会重复,dp状态计算的实际上是金牌线不超过i,铜牌线不小于j的方案数

所以我们可以考虑容斥原理,用(i,j)-(i-1,j)-(i,j+1)+(i-1,j+1)来得到最后的答案

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=1e9;
const int magic=3048;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n,m;
int ga[58][58];
int g1,g2,s1,s2;
int fastest[58],slowest[58];
bool G[58],S[58],B[58];
LL dp[58][58][58];

LL solve(int goldline,int bronzeline,int silverup,int silverdown)
{
	memset(G,false,sizeof(G));
	memset(S,false,sizeof(S));
	memset(B,false,sizeof(B));
	int i,j,k;
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
			if (i!=j)
			{
				if (ga[i][j]<=goldline) G[i]=true;
				if (ga[i][j]>=bronzeline) B[i]=true;
				if (ga[i][j]>silverup && ga[i][j]<silverdown) S[i]=true;
			}
	memset(dp,0,sizeof(dp));
	dp[0][0][0]=1;
	for (i=1;i<=n;i++)
		for (j=0;j<=i;j++)
			for (k=0;j+k<=i;k++)
			{
				if (G[i]) dp[i][j][k]+=dp[i-1][j-1][k];
				if (S[i]) dp[i][j][k]+=dp[i-1][j][k-1];
				if (B[i]) dp[i][j][k]+=dp[i-1][j][k];
			}
	LL res=0;
	for (i=g1;i<=g2;i++)
		for (j=s1;j<=s2;j++)
			res+=dp[n][i][j];
	return res;
}

void floyd()
{
	int i,j,k;
	for (k=1;k<=n;k++)
		for (i=1;i<=n;i++)
			for (j=1;j<=n;j++)
				if (k!=i && k!=j && i!=j && ga[i][k]+ga[k][j]<ga[i][j])
					ga[i][j]=ga[i][k]+ga[k][j];
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
			if (ga[i][j]<INF) ga[i][j]=ga[i][j]*n+i;
}

int main ()
{
	int i,j,x,y,c;
	n=getint();m=getint();
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
			ga[i][j]=(i==j?0:INF);
	for (i=1;i<=m;i++)
	{
		x=getint();y=getint();c=getint();
		ga[x][y]=c;ga[y][x]=c;
	}
	g1=getint();g2=getint();s1=getint();s2=getint();
	floyd();
	for (i=1;i<=n;i++)
	{
		int maxlen=-INF,minlen=INF;
		for (j=1;j<=n;j++)
			if (i!=j) maxlen=max(maxlen,ga[i][j]),minlen=min(minlen,ga[i][j]);
		fastest[i]=minlen;slowest[i]=maxlen;
	}
	LL ans=0;
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
			ans=ans+solve(fastest[i],slowest[j],fastest[i],slowest[j])-solve(fastest[i]-1,slowest[j],fastest[i],slowest[j])-solve(fastest[i],slowest[j]+1,fastest[i],slowest[j])+solve(fastest[i]-1,slowest[j]+1,fastest[i],slowest[j]);
	printf("%lld\n",ans);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值