C题链接
D题链接
E题链接
C题思路:
我们设相邻的两个-1的位置是的值是l和r,他们直接的距离是d(也就是r的下标减l的下标)。
思路1:直接模拟操作,看所有操作里是否有合法操作。
比如1 -1 -1 -1 -1 -1 7. 容易想到1*2+1=3,3*2+1=7,那么剩下的三个位置我们可以先乘2再除以2,最后到r位置的时候仍然是7.也就是说对于一般情况下,我们可以先用前面的位置构造出r的值,然后再用多余的-1来循环乘除,就可以消掉多余的-1.同时容易发现,如果多余的-1是偶数(包括0),这样循环就有问题,所以合法情况的多余-1一定是奇数个。
那么我们怎么判断上面那种情况是否存在合法情况呢。我们可以枚举,因为都是乘2或者除以2,最多log次,我们直接枚举所有情况。比如1到7,我们实际上乘了2次2。但这时候我们会发现,乘完两次2之后才4,仍然差3,但是我们要是每次都乘2+1又刚刚好到7,那么这些可达数有什么关系,又是否连续,以及要怎么规划顺序呢。
举个例子。2*2*2=8, 2*2*2+1=9,(2*2+1)*2=10。我们可以发现乘2+1如果出现在最后一位加的是1,出现在倒数第二位加的是2,也就是说,倒数第几次出现*2+1就会比全乘2的结果多出一次2的幂。再通过其他例子,还可以发现,这个结果是可加的,比如(2*2+1)*2+1=11,多了2^0+2^1,所以实际上结果是连续的,且与*2+1出现的地方有关,只需要r在【l*2^d,l*2^d+2^d-1】区间就可以构造。
这时候我们已经得到了一些情况怎么处理和构造了,但如果是3到9呢?这时候我们如果还是只通过上面的*2或者*2+1是构造不出来9的。经过尝试,我们可以这样构造:3 -> 1 -> 2 -> 4 -> 9。也就是说其实我们是可以先将3/2再进行乘的操作的,且容易知道只需要第一次除完,后续就不需要再除了,因为后面如果我们乘完再除,实际上等价于不乘。
同理,我们枚举除的次数,然后再枚举乘的次数,这样时间复杂度也是可以的。
思路2:可以发现*2和*2+1或者/2都是二叉树操作,从l到r实际上就是走二叉树路径
直接给官方题解(其实就是类似倍增lca找公共祖先,先把dep大的除2到两个dep一样,再一起除,然后找lca)
代码1:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
const int mod=1e9+7;
const ll INF=2e9+10;
mt19937_64 rd(23333);
uniform_real_distribution<double> drd(0.000001,0.99999);
int n,a[N];
void solve(){
cin>>n;
int l=-1,r=-1;
vector<int> v;
for(int i=1;i<=n;++i){
cin>>a[i];
if(a[i]!=-1){
if(l==-1)
l=i;
r=i;
v.push_back(i);
}
}
if(l==-1){
for(int i=1;i<=n;++i){
cout<<(i&1)+1<<' ';
}
cout<<endl;
return;
}
for(int i=l-1;i;--i)
a[i]=(((l-i)&1)?a[l]*2:a[l]);
for(int i=r+1;i<=n;++i)
a[i]=(((i-r)&1)?a[r]*2:a[r]);
for(int i=1;i<(int)v.size();++i){
int l=v[i-1],r=v[i];
if(l==r-1){
if(a[r]==a[l]/2||a[l]==a[r]/2)
continue;
cout<<-1<<endl;
return;
}
int d=r-l;
int tl=a[l],tr=a[r];
int flag=-1,pos=-1,sub=0;
for(;tl;tl>>=1,--d,++sub){
for(int j=0;j<=d;++j){//2^j+y==d(mod 2) y=[0,2^j -1]
if((1ll<<j)>tr) break;
if((d-j)&1) continue;
if(tl*(1ll<<j)<=tr&&tl*(1ll<<j)+(1ll<<j)-1>=tr){
flag=1,pos=j;
break;
}
}
if(flag!=-1) break;
}
if(flag==-1){
cout<<-1<<endl;
return;
}
if(sub){
for(int j=1;j<=sub;++j)
a[j+l]=a[j+l-1]/2;
}
int need=tr-(1ll<<pos);
int p;
p=pos-1;
for(int j=l+sub+1;j<=min(r-1,l+pos+sub);++j,--p){
a[j]=a[j-1]*2+((need>>p)&1);
}
for(int j=l+pos+sub+1,o=1;j<r;++j,++o){
a[j]=((o&1)?a[j-1]*2:a[j-1]/2);
}
}
for(int i=1;i<=n;++i)
cout<<a[i]<<' ';
cout<<endl;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}
题解代码:
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 200100;
int n, a[maxn];
inline vector<int> path(int x, int y) {
vector<int> L, R;
while (__lg(x) > __lg(y)) {
L.pb(x);
x >>= 1;
}
while (__lg(y) > __lg(x)) {
R.pb(y);
y >>= 1;
}
while (x != y) {
L.pb(x);
R.pb(y);
x >>= 1;
y >>= 1;
}
L.pb(x);
reverse(R.begin(), R.end());
for (int x : R) {
L.pb(x);
}
return L;
}
void solve() {
scanf("%d", &n);
int l = -1, r = -1;
vector<int> vc;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if (a[i] != -1) {
if (l == -1) {
l = i;
}
r = i;
vc.pb(i);
}
}
if (l == -1) {
for (int i = 1; i <= n; ++i) {
printf("%d%c", (i & 1) + 1, " \n"[i == n]);
}
return;
}
for (int i = l - 1; i; --i) {
a[i] = (((l - i) & 1) ? a[l] * 2 : a[l]);
}
for (int i = r + 1; i <= n; ++i) {
a[i] = (((i - r) & 1) ? a[r] * 2 : a[r]);
}
for (int _ = 1; _ < (int)vc.size(); ++_) {
int l = vc[_ - 1], r = vc[_];
vector<int> p = path(a[l], a[r]);
if (((int)p.size() & 1) != ((r - l + 1) & 1) || r - l + 1 < (int)p.size()) {
puts("-1");
return;
}
for (int i = 0; i < (int)p.size(); ++i) {
a[l + i] = p[i];
}
for (int i = l + (int)p.size(), o = 1; i <= r; ++i, o ^= 1) {
a[i] = (o ? a[i - 1] * 2 : a[i - 1] / 2);
}
}
for (int i = 1; i <= n; ++i) {
printf("%d%c", a[i], " \n"[i == n]);
}
}
int main() {
int T = 1;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
D题思路:
对于合数4*9=6*6,尽管两个数都不一样但他们的乘积可能一样,不好构造。但如果是两个素数,只有两个素数都相等时,他们的乘积才相等,所以我们考虑取素数来构造。
如果我们现在有x个素数,类似完全图的连边,这里有n*(n-1)/2+n条无向边,因为每个素数还可以和自己乘,也就是这么多对乘积。因为题目要求任意两个相邻的乘积都不能相等,转化成图论问题,也就是说从n*(n-1)/2+n条边的完全图去找一个起点出发,不经过相同边的最长路径是多长(因为只要不是同一个边,那么边两边的素数一定不会全相同,乘积就不会相同),我们一定要是一条路径上是因为这是一个数组,然后每条边对应着数组相邻的两个点,也就是说一定是一条边的终点就是另一条边的起点,也就是说这些边一定是相连成一条路径的。
不经过同一条边的路径容易让我们想到欧拉路:
欧拉路:欧拉路是指从图中任意一个点开始到图中任意一个点结束的路径,并且图中每条边通过的且只通过一次。
欧拉回路:欧拉回路是指起点和终点相同的欧拉路。
存在欧拉路的条件:
1.无向连通图存在欧拉路的条件:
所有点度都是偶数,或者恰好有两个点度是奇数,则有欧拉路,若无奇数点度,则为欧拉回路。若有奇数点度,则奇数点度点一定是欧拉路的起点和终点,否则可取任意一点作为起点。
2.有向连通图存在欧拉路的条件:
- 每个点的入度等于出度,则存在欧拉回路(任意一点有度的点都可以作为起点)
- 除两点外,所有入度等于出度。这两点中一点的出度比入度大,另一点的出度比入度小,则存在欧拉路。取出度大者为起点,入度大者为终点。
可以知道不经过同一条边的最长路径其实就是欧拉路,所以我们只要找x个点最大欧拉路是多少即可。
顺便贴一下官方题解
输出欧拉回路的时候要逆序输出,看看这个
欧拉路径的题目:UVA 10054 The Necklace
欧拉回路路径的注意点:
for(i=1; i<=50; i++)
euler(i);
void euler(int u)
{
int v;
for(v=1; v<=50; v++)
if(g[u][v])
{
g[u][v]--;
g[v][u]--;
euler(v);
printf("%d %d\n",v,u);
//一定要逆序输出,而且注意输出的边是(v,u)而不是(u,v)
}
}
如果写成这样是错的
void euler(int u)
{
int v;
for(v=1; v<=50; v++)
if(g[u][v])
{
g[u][v]--;
g[v][u]--;
printf("%d %d\n",u,v);
euler(v);
//这样相当于顺序输出
}
}
在输入的时候使会有重边的,也就是g[i][j]的值不一定只是为1
然后从一个点出发,找到和他相连的点,然后删除这条无向边,所以是 g[u][v]--; g[v][u]--; 然后就去dfs下一个点v,最后在递归返回的时候才输出路径,也就是逆序输出,为什么要逆序输出了
因为和当前点i相连的点可能不止一个
例如当前点是1,上一条边是(3,1) . 而和1相连的点有2,7,11,能分成3个方向
往2的方向有:(1,2) (2,4)
往7的方向有:(1,7)(7,5)(5,6)
往11的方向有:(1,11)(11,12)(12,13)
如果顺序输出将会是
3 1
1 2
2 4
1 7
7 5
5 6
1 11
11 12
12 13
当找到起点之后,将起点压入栈中,然后访问与顶点相连的一个顶点,将该顶点压入栈中,同时删除这条边,然后继续DFS寻找顶点,并同样压栈、删除,最后,直到走到一个没有任何边与它相连的顶点(可能是起始点,也可能不是),便开始进行回溯,(回溯的同时进行弹栈,弹栈的结果也就是欧拉回路的逆序输出结果),回溯的过程就是寻找相连路径的过程,如果回溯的过程中发现仍然有边与当前顶点相连,那么继续从这个顶点沿着未删除的边去DFS,同时进行压栈等一系列操作,最后,必定会回到该点,然后继续回溯,直到顶点,逆序输出,结束
题解代码:
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<int, int> pii;
const int maxn = 4000100;
const int N = 1000000;
int n, a[maxn], pr[maxn], tot, stk[maxn], top;
bool vis[maxn];
inline void init() {//筛素数
for (int i = 2; i <= N; ++i) {
if (!vis[i]) {
pr[++tot] = i;
}
for (int j = 1; j <= tot && i * pr[j] <= N; ++j) {
vis[i * pr[j]] = 1;
if (i % pr[j] == 0) {
break;
}
}
}
mems(vis, 0);
}
inline bool check(int x) {
if (x & 1) {
return x + 1 + x * (x - 1) / 2 >= n;
} else {
return x * (x - 1) / 2 - x / 2 + 2 + x >= n;
}
}
vector<pii> G[10000];
void dfs(int u) {
while (G[u].size()) {
pii p = G[u].back();
G[u].pop_back();
if (vis[p.scd]) {//标记了来边和回边
continue;
}
vis[p.scd] = 1;
dfs(p.fst);
}
stk[++top] = pr[u];
}
void solve() {
scanf("%d", &n);
int l = 1, r = 10000, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
for (int i = 1; i <= ans; ++i) {
vector<pii>().swap(G[i]);
}
int tot = 0;
for (int i = 1; i <= ans; ++i) {
for (int j = i; j <= ans; ++j) {
if (ans % 2 == 0 && i % 2 == 0 && i + 1 == j) {
continue;
}
G[i].pb(j, ++tot);
G[j].pb(i, tot);
}
}
for (int i = 1; i <= tot; ++i) {
vis[i] = 0;
}
top = 0;
dfs(1);
reverse(stk + 1, stk + top + 1);
for (int i = 1; i <= n; ++i) {
printf("%d%c", stk[i], " \n"[i == n]);
}
}
int main() {
init();
int T = 1;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
E题思路:
如果现在有3个区间a,b,c相交,且v[a]<v[b]<v[c],他们的权值是从小到大的。因为边权是|v[x]-v[y]|,也就是说边ac的边权是v[c]-v[a],而ab和bc的权值是v[b]-v[a],v[c]-v[b],可以发现连接ab和bc的边权和等于连接ac的边权,而前者已经连接了三点,而后者只连接了ac点,所以得出结论:
对于点a与点集s[b,c,d,e,f.....]连边,只会连接点集s以权值排序a权值的前后继。容易知道如果后继的后继与a连边不如a与后继连边,后继与后继的后继连边。
那么现在问题转化成怎么找每个相交区间的前后继问题。
我们可以用扫描线思想,将每个区间分为两个时间点,一个左端点表示出现,一个右端点表示结束。我们按照时间顺序往一个数据结构里放区间时间端点,左端点标号为正进去结构,右端点标号为负,如果出现右端点就将该区间从结构里删去,也就是删去左端点。
如果我们将时间点排序,每次新添一个区间的时候,结构里存在的区间一定是l<=当前区间的l,并且他们的r>=当前区间的l,也就是说当前区间一定是和结构里的所有区间相交的,我们只需要考虑当前集合对于当前新添区间的前后缀。
当前集合里的区间只包括了当前区间以前的相交区间,并不会考虑到后面相交区间,会不会有影响?实际上不会,因为当后面相交区间再添加的时候,如果它与当前区间更近的话,这条边会被后来区间被添加的时候考虑,如果没被考虑就表示最小生成树的并不会有这条边,这样最多添加2*n条边,就可以直接跑生成树算法。(因为边是无向边和无序点对一样,<a,b>和<b,a>只要遍历一次就够了,所以如果我们要找n个数的所有无序点对,只需要让每个点和它之前的数取点对即可,后面的点自然会遍历到当前点,如果让当前数又去后后面取点对,实际上是选了<a,b>,<b,a>,如果是无序点对,相当于取了两次,其实只往前面取点对,就是对点对的first做了规定,如果是数组,就是first的数组下标要小于second,这样就可以去重了,去掉了second大于first的点对,这样也是取完所有点对了)
因为我们要维护的是点权排序,所以我们采用set自动排序点权,因为我们要记录边,所以使用pair,第二维记录点的编号,又因为l与r相等的时候也算区间相交,所以我们将r+1,不然会被提前删除,与后面l等于当前r的区间相交情况就没了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
const int inf=0x3f3f3f3f;
const int N=5e5+10;
const int mod=1e9+7;
const ll INF=2e9+10;
mt19937_64 rd(23333);
uniform_real_distribution<double> drd(0.000001,0.99999);
int n;
int fa[N];
int find(int x){
if(x==fa[x])
return x;
return fa[x]=find(fa[x]);
}
void u(int a,int b){
a=find(a),b=find(b);
if(a!=b)
fa[a]=b;
}
struct node{
int l,r,x;
}a[N];
struct nod{
int a,b,v;
bool operator<(nod t){
return v<t.v;
}
};
void solve(){
cin>>n;
vector<pii> v;
for(int i=1;i<=n;i++){
cin>>a[i].l>>a[i].r>>a[i].x;
v.push_back({a[i].l,i});
v.push_back({++a[i].r,-i});//l==r也算相交
}
vector<nod> e;
sort(v.begin(),v.end());//让区间从左到右,从小到大
set<pii> s;//first是a【i】权值x,second是区间编号
for(auto &x:v){
if(x.second>0){//这个区间的开端
s.insert({a[x.second].x,x.second});
auto pos=s.find({a[x.second].x,x.second});
if(pos!=s.begin()){
int y=prev(pos)->second;
nod t={x.second,y,abs(a[x.second].x-a[y].x)};
e.push_back(t);
}
if(next(pos)!=s.end()){
int y=next(pos)->second;
nod t={x.second,y,abs(a[x.second].x-a[y].x)};
e.push_back(t);
}
}
else{
s.erase({a[-x.second].x,-x.second});
}
}
for(int i=1;i<=n;i++)
fa[i]=i;
sort(e.begin(),e.end());
int cnt=0,ans=0;
for(auto &x:e){
if(find(x.a)!=find(x.b)){
u(x.a,x.b);
cnt++;
ans+=x.v;
}
}
if(cnt!=n-1)
cout<<-1<<endl;
else
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}