Regular Contest 102 - D - All Your Paths are Different Lengths
题意
给定一个(L),要求构造出一个点数(n)不超过(20),边数(m)不超过(60)的有向图(允许存在重边)
保证该图从点(1)到点(n)存在恰好(L)种不同的路径,每条路径权值和互异,且在([0,L-1])之间
限制
(2leq Lleq 10^6)
思路
第一部分
考虑([0,L-1])内数字的二进制表示法
由于每个数字都能表示成多个(2)的次方幂之和,换句话说,为了表示出某个数字,对于每个(2)的次方幂可以决定是选还是不选(二进制上(1)表示选择,(0)表示不选)
以有向图进行表示,则可以按顺序连接两条(i)到(i+1)的边,一条权值为(2^{i-1})表示选择,一条权值为(0)表示不选择
得到的有向图如下图所示:
上图从第(1)个点走到第(7)个点,所能表示的数值范围为([0,111111B=63])
第二部分
但发现题目限制(nleq 20),因此最多只能够表示到(2^{18}=262144)过,此时整张图(20)个点所能表示的数字范围为([0,2^{19}-1=524287]),无法满足(L=1000000)的情况
前面提及:
- 如果只考虑第(1)个点到第(i)个点,其所能表示出的数字范围为([0,2^{i-1}-1])
而如果给这个范围加上一个常数(a),就变得能够表示([a,a+2^{i-1}-1])内的数字了
假设此前我们所能表示出的数字为([0,limit-1]),以(limit)变量表示此时最小的不能表示出的数字
以(limit)变量作为常数,那就可以将([limit,limit+2^{i-1}-1])之内的数字表示出来了
换在构造的有向图中,这也就表示自第(i)个点向第(n)个点连一条权值为(limit)的边
建边后,(limit)也就变成了(limit+2^{i-1})
下图表示(L=74)(边权和范围为([0,73]))所构造出的有向图:
第三部分
上述方法实际上也就是考虑(L-limit)的二进制表示法上(1)所在的位置,并将其与终点连边
所以需要让(i)从大到小遍历去判断是否第(1)个位置到第(i)个位置所能表示的数字数量((2^{i-1}))小于剩余的不能表示出的数字数量((L-limit))
因此,本题只需要循环遍历两遍即可,具体实现方式详见代码部分
本思路点数(n)最多为(20),边数最多为((20-1)*2+19leq 60),满足题意
程序
#include<bits/stdc++.h>
using namespace std;
struct node
{
int u,v,w;
node(){}
node(int u,int v,int w):u(u),v(v),w(w){}
};
vector<node> ans;
int L,mx,sum[25];
int main()
{
scanf("%d",&L);
L--;
sum[1]=0;
for(int i=1;i<=20;i++)
{
sum[i+1]=sum[i]+(1<<(i-1)); //到第i+1个点所能表示出的最大数字
if(sum[i+1]<=L) //如果在L-1的范围内
{
ans.push_back(node(i,i+1,1<<(i-1))); //两点间进行2的次方幂连边
ans.push_back(node(i,i+1,0)); //不选
mx=i+1;
}
else
break;
}
int limit=sum[mx]+1; //limit用于表示此时最小的不能表示出的数字
for(int i=mx;i;i--)
{
if(sum[i]<=L-limit) //如果连一条limit的边,不会超出范围
{
ans.push_back(node(i,mx,limit));
limit+=sum[i]+1; //注意+1
}
}
printf("%d %d
",mx,ans.size());
for(node &nd:ans)
printf("%d %d %d
",nd.u,nd.v,nd.w);
return 0;
}