H题题解
链接:题目
题目大意:给你一个数L,让你用最多20个点,60条边建立一个有向图,让从1到n的不同路径恰好为L,且值分布为0-L-1。
要求:可以有重边但不能有自环,边都是由小指向大的。
L的范围:
2
≤
L
≤
1
0
6
2\leq L\leq 10^{6}
2≤L≤106
解析:首先我们想到用二进制去思考这个问题,
2
20
2^{20}
220刚好是1e6,然后对于这个题意我们就能用38条边去表示每一位取不取,即假设点20为1,19为2,18为4……,1为
2
19
2^{19}
219,然后对于每个点都往后连一条0和一条值为后面点权值的边表示不取和取。其中会发现点1没有取的边,这个后面能处理。
接下来就是类似用数位dp处理0到n有多少个数的思想处理这个问题。
对于一个二进制为101101的数,我们从点15到点20连边,然后从前往后对于每一位是1的数我们考虑这个位置取0和取1的情况。比如第1个1,也就是点15,取0那么后面的数就可以任意选,所以我们就可以从点1往点15连一条值为0的边,取1的情况我们先把数用一个he累积下来,继续往下细分,后面的位置如果是0,就取0,到了点17也就第二个1,如果取0那么就可以任意取,我们从点1往点17连值为he的边,也就是前面的位置能取1就取1的情况,取1的情况继续往后推。
显然每个点最多从1连过来一条边,故答案不会超过60.特别需要注意因为这题不能自环,所以1向1连边需要处理掉。
开头我们说点1没有连向他的边,但因为点1永远是首位,他的取0分支的he为0,也就是有他的情况永远在取1分支中,这所有数首位都是如此。故没有问题。
#include<bits/stdc++.h>
using namespace std;
const int N=1<<21;
int main(){
long long a;
cin>>a;
vector<int>t;
long long b=a;
int idx=0;
while(a){
if(a&1)idx++;
t.push_back(a%2);
a/=2;
}
idx+=2*(t.size()-1);//特判1 1
if(b&(1<<19))idx--;
printf("20 %d\n",idx);
for(int i=20-t.size()+1;i<20;i++){
printf("%d %d 0\n",i,i+1);
}
for(int i=20-t.size()+1;i<20;i++){
printf("%d %d %d\n",i,i+1,N>>(i+2));
}
long long p=0;
for(int j=1;j<=20;j++){
int d=N>>(j+1);
if(b&d){
if(j!=1)printf("1 %d %lld\n",j,p);
p+=d;
}
}
return 0;
}