做出TC Div1 medium真是开心,尽管一般来说都是450的较水题qwq
不过是做出了一直不擅长的构造欸
对于此题,我们要发现几个性质。
- 性质1:每一轮操作过后联通块个数起码减少一半
- 性质2:操作次数只与MST上的边有关
性质1先判掉无解。性质2告诉我们不在MST上的边随便连连就好了。于是我们干脆让MST上的 n − 1 n-1 n−1条边就是最小的 n − 1 n-1 n−1条边,并且这个MST是一条链。
由于合并 k k k次,我们就只需考虑这条链上的前 2 k 2^k 2k个结点,其余结点就令 a [ i ] [ i + 1 ] = i a[i][i+1]=i a[i][i+1]=i( a [ i ] [ j ] a[i][j] a[i][j]表示 i i i, j j j之间边权),这样就可以让其余结点在第一轮就合并到第 2 k 2^k 2k个结点上。
然后对于前
2
k
2^k
2k个结点,我们要让它们恰好合并
k
k
k次,大概就是每次第一个联通块和第二个合并,第三个和第四个合并……
构造的(伪)代码大概长这样
int cnt=0;
for(int i=1;i<=k;i++)
for(int j=1<<(i-1);j<s;j+=1<<i)
a[j][j+1]=++cnt;
不理解就画个图吧…
比如
k
=
3
k=3
k=3,构造出来的图就是这样的:
O——O——O——O——O——O——O——O
1
5
2
7
3
6
4
\ \ \ \ \ 1\ \ \ \ \ \ \ \ 5\ \ \ \ \ \ \ 2\ \ \ \ \ \ \ 7\ \ \ \ \ \ \ 3\ \ \ \ \ \ \ 6\ \ \ \ \ \ \ 4
1 5 2 7 3 6 4
然后会发现每次就成功两个两个合并了qwq
#include <bits/stdc++.h>
#define ll long long
#define fr(i,x,y) for(int i=x;i<=y;i++)
#define rf(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
const int N=1001;
template<class T> void checkmin(T &a,const T &b) { if (b<a) a=b; }
template<class T> void checkmax(T &a,const T &b) { if (b>a) a=b; }
class BearSpans {
public:
vector <int> findAnyGraph( int n, int m, int k ) ;
};
int isok(int n,int k){
int s=1;
fr(i,1,k){
s<<=1;
if (s>n) return 0;
}
return s;
}
vector <int> BearSpans::findAnyGraph(int n, int m, int k) {
if (!isok(n,k)) return {-1};
int s=isok(n,k);
vector<int> ans;
fr(i,1,k)
for(int j=1<<(i-1);j<s;j+=1<<i)
ans.push_back(j),ans.push_back(j+1);
fr(i,s,n-1) ans.push_back(i),ans.push_back(i+1);
int z=m-(n-1),cnt=0;
if (cnt==z) return ans;
fr(i,1,n)
fr(j,i+2,n){
ans.push_back(i);ans.push_back(j);
cnt++;
if (cnt==z) return ans;
}
return ans;
}