Codeforces Round 949 (Div. 2) C.D构造和E题

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;
}

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值