- 给定(L,R),你可以用至多(32)个点构造一张简单(DAG),并给每条边定一个正边权。
- 要求使得(1 ightarrow n)所有路径长度构成的集合恰好是([L,R])。
- (1le Lle Rle10^6)
二进制分解
这种题目应该很容易往二进制分解的方向去想。
首先说明,无论(L,R)取何值,答案都一定是(YES),且按照我的方法应该只要让(n=22)就可以了。
考虑我们先从(1)号点向所有点连一条长度为(L)的边,现在问题就在于如何构造出值域为([1,R-L])的路径长度。
对于所有(iin[2,20]),从(i)号点向之后除(n)号点以外所有点连一条长度(2^{i-2})的边。
容易发现,此时(1 ightarrow i)的路径长度值域其实是([L,L+2^{i-2}-1])。
接下来就是要考虑怎么用它们来凑出(R-L),实际上我们可以直接让(23)号点成为(n)号点。
我们枚举(iin[2,21]),如果(R-L)第(i-2)位是(1),则令(t)表示将(R-L)末(i-2)位都修改为(0)后的值,然后我们就从(i)向(n)号点连一条权值为(t+1)的边。这样一来(1 ightarrow n)路径长度的值域就添上了([L+t+1,L+t+2^{i-2}])
具体的理解最好还是自己画个图比较好。
代码:(O(20^2))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 32
#define Add(x,y,z) (tot+=sprintf(s+tot,"%d %d %d
",x,y,z),++cnt)//存下一条边的输出,因为要先输出边数
using namespace std;
int L,R,cnt,tot;char s[10000000];
int main()
{
RI i,j,t;scanf("%d%d",&L,&R),puts("YES"),Add(1,22,L);//从1号点向n号点连边
for(i=1;i<=20;++i) for(t=i^1?1<<i-2:L,j=i+1;j<=21;++j) Add(i,j,t);//从1~20号点向之后除n号点以外的点连边
for(t=R-L,i=2;i<=21;++i) t>>(i-2)&1&&(t^=1<<i-2,Add(i,22,t+1));//将R-L二进制分解
End:return printf("22 %d
",cnt),puts(s),0;//输出构造方案
}