2021.9.8 模拟赛

前言

手残+脑残=我。

T1 [SHOI2016]随机序列 加强版

题目

洛谷(弱化版)

加强版数据有 (a_i=0) 的情况,考试没过是因为有个地方的 (-1) 打掉了,然而可以过没 (0) 的情况,所以样例没出锅。

加强版
//12252024832524
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 100005;
const int MOD = 1e9 + 7;
int n,m;
int a[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int qpow(int x,int y)
{
	int ret = 1;
	while(y){if(y & 1) ret = 1ll * ret * x % MOD;x = 1ll * x * x % MOD;y >>= 1;}
	return ret;
}

#define lc (x<<1)
#define rc (x<<1|1)
int s[MAXN << 2],val[MAXN << 2],lz[MAXN << 2],PRE = 1;
int Ad(int x){if(x >= MOD) x -= MOD; if(x < 0) x += MOD; return x;}
void up(int x){s[x] = Ad(s[lc] + s[rc]);}
void calc(int x,int val){s[x] = 1ll * s[x] * val % MOD;lz[x] = 1ll * lz[x] * val % MOD;}
void down(int x)
{
	if(lz[x] == 1) return;
	calc(lc,lz[x]);
	calc(rc,lz[x]);
	lz[x] = 1;
}
void Build(int x,int l,int r)
{
	lz[x] = 1;
	if(l == r)
	{
		PRE = 1ll * PRE * a[l] % MOD;
		if(l < n) s[x] = 2ll * PRE * qpow(3,n-l-1) % MOD;
		else s[x] = PRE;
		return;
	}
	int mid = (l+r) >> 1;
	Build(lc,l,mid); Build(rc,mid+1,r);
	up(x);
}
void Mul(int x,int l,int r,int ql,int qr,int v)
{
	if(ql <= l && r <= qr)
	{
		calc(x,v);
		return;
	}
	down(x);
	int mid = (l+r) >> 1;
	if(ql <= mid) Mul(lc,l,mid,ql,qr,v);
	if(mid+1 <= qr) Mul(rc,mid+1,r,ql,qr,v);
	up(x);
}
int Query(int x,int l,int r,int ql,int qr)
{
	if(ql > qr) return 0;
	if(ql <= l && r <= qr) return s[x];
	down(x);
	int mid = (l+r) >> 1,ret = 0;
	if(ql <= mid) ret += Query(lc,l,mid,ql,qr);
	if(mid+1 <= qr) ret += Query(rc,mid+1,r,ql,qr);
	return Ad(ret);
}

set<int> cao;
set<int>::iterator it;

int main()
{
//	freopen("task.in","r",stdin);
//	freopen("task.out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1;i <= n;++ i) 
	{
		a[i] = Read();
		if(!a[i]) a[i] = 1,cao.insert(i);
	}
	Build(1,1,n);
	cao.insert(n+1);
	for(int i = 1;i <= m;++ i)
	{
		int pos = Read(),v = Read();
		if(!v) cao.insert(pos);
		else
		{
			it = cao.lower_bound(pos);
			if((*it) == pos) cao.erase(it);
			Mul(1,1,n,pos,n,1ll * qpow(a[pos],MOD-2) * v % MOD);
			a[pos] = v;
		}
		it = cao.begin();
		Put(Query(1,1,n,1,*it-1),'
');//曾经,这里没有-1 
	}
	return 0;
}

题解

挖掘性质后发现能加就能减,对答案没有贡献,所以只考虑前缀中没有加减的情况,即只有乘法。

直接线段树维护一下即可,有 (0) 就相当于在中间夹断,求答案的时候不全局求。

T2 牛客第五场A题 Portal

题目

牛客

由于我看的可能是魔改过后的题目,题意可能会有区别,但是本质是一样的。

题解

首先我们把取货点和送货点都看成必经打卡点。

这道题的启发,可知我们DP不需要记录当前人在哪里,因为一定在某个打卡点处。

然后挖掘性质,发现如果我们要使用传送门,原地开一个当起点就好,所以只用记录一个传送门的位置。

然后就可以 (O(n^2k)) DP了,数组可以滚动,但没必要,但如果不滚动记得两倍空间。

没滚动的代码
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 305;
const LL INF = 1ll << 60;
int n,m,s;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

LL dis[MAXN][MAXN],dp[MAXN << 1][MAXN];//任务i,传送门在j 
int pos[MAXN << 1];

int main()
{
//	freopen("express.in","r",stdin);
//	freopen("express.out","w",stdout);
	n = Read(); m = Read(); s = Read() << 1;
	for(int i = 1;i <= n;++ i)
		for(int j = 1;j <= n;++ j)
			if(i^j) dis[i][j] = INF;
	for(int i = 1,u,v;i <= m;++ i)	
		u = Read(),v = Read(),dis[u][v] = dis[v][u] = Min(dis[u][v],Read());
	for(int k = 1;k <= n;++ k)
		for(int i = 1;i <= n;++ i)
			for(int j = 1;j <= n;++ j)
				dis[i][j] = Min(dis[i][j],dis[i][k]+dis[k][j]);
	for(int i = 1;i <= s;++ i) pos[i] = Read();
	memset(dp,0x3f,sizeof(dp));
	for(int i = 1;i <= n;++ i) //枚举传送门 
		for(int j = 1;j <= n;++ j)//中转传送门 
			dp[1][i] = Min(dp[1][i],dis[1][j]+dis[i][j]+Min(dis[i][pos[1]],dis[j][pos[1]]));
	for(int i = 2;i <= s;++ i)
		for(int j = 1;j <= n;++ j)
		{
			dp[i][j] = Min(dp[i][j],dp[i-1][j]+dis[pos[i-1]][pos[i]]);
			for(int k = 1;k <= n;++ k)
				dp[i][k] = Min(dp[i][k],dp[i-1][j]+dis[j][k]+Min(dis[k][pos[i]],dis[j][pos[i]])),//通过传送门去放传送门 
				dp[i][k] = Min(dp[i][k],dp[i-1][j]+dis[pos[i-1]][k]+Min(dis[k][pos[i]],dis[j][pos[i]]));//走去放传送门,再通过传送门到当前点 
		}
	LL ans = INF;
	for(int i = 1;i <= n;++ i) ans = Min(ans,dp[s][i]);
	Put(ans);
	return 0;
}

T3 牛客第五场B题 Graph

题目

牛客

此条目同上。

题解

挖掘性质后发现其实就是最小异或生成树,然后我不会,只能糊一个 ( t Prim) 上去拿了个 (O(n^2))(70pts)

正解是改进的 ( t Boruvka),每轮操作可以不用 (O(m)) 枚举,而是 (O(nlog_2a_i)) 在字典树或者权值线段树上查询。

由于要动态维护连通性,还要询问异或最小,所以用并查集+线段树合并,用主席树查询的思想做就行了。

还有一个常数小和代码短的做法是权值分治,考虑分 (log_2a_i) 层,每层按当前层分为 (01) 两部分,显然层级越高我们希望连的边越少,所以让左右子树分别连通之后左右 (01) 两部分只有连 (1) 条边即可,至于连的边嘛,当然是一半插入 Trie ,另一半查询咯。

两个做法时间复杂度是一样的,都是 (O(nlog_2nlog_2a_i))

线段树合并大常数做法
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 200005;
const int INF = 1 << 30;
int n;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[MAXN],etot;
struct edge
{
	int v,w,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y,int z)
{
	e[++etot].v = y;
	e[etot].w = z;
	e[etot].nxt = head[x];
	head[x] = etot;
}
void Add_Double_Edge(int x,int y,int z)
{
	Add_Edge(x,y,z);
	Add_Edge(y,x,z);
}
int d[MAXN],val[MAXN];
void dfs(int x)
{
	for(int i = head[x]; i ;i = e[i].nxt)
		if(!d[e[i].v])
			d[e[i].v] = d[x] + 1,val[e[i].v] = val[x] ^ e[i].w,dfs(e[i].v);
}
bool vis[MAXN];
int od[MAXN],f[MAXN];
bool cmp(int x,int y){return val[x] < val[y];}
int findSet(int x)
{
	if(f[x]^x) f[x] = findSet(f[x]);
	return f[x];
}

int rt[MAXN],tot,who;
int ch[MAXN*60][2],siz[MAXN*60],ID[MAXN*60];
void Add(int &x,int v,int rk)
{
	if(!x) x = ++tot;
	++siz[x];
	if(rk < 0) {ID[x] = who;return;}
	bool to = v >> rk & 1;
	Add(ch[x][to],v,rk-1);
}
int mge(int x,int y)
{
	if(!x || !y) return x|y;
	siz[x] += siz[y];
	ch[x][0] = mge(ch[x][0],ch[y][0]);
	ch[x][1] = mge(ch[x][1],ch[y][1]);
	return x;
}
void unionSet(int u,int v)
{
	u = findSet(u); v = findSet(v);
	if(u^v) f[v] = u,rt[u] = mge(rt[u],rt[v]);
}
int MIN[MAXN],CID[MAXN],ori;
void Query(int lst,int x,int v,int rk)
{
	if(v >= MIN[who]) return;
	if(rk < 0)
	{
		CID[who] = ID[lst];
		MIN[who] = v;
		return;
	}
	bool to = val[ori] >> rk & 1;
	if(siz[ch[lst][to]] - siz[ch[x][to]]) Query(ch[lst][to],ch[x][to],v,rk-1);
	else Query(ch[lst][to^1],ch[x][to^1],v|(1<<rk),rk-1);
}

int main()
{
//	freopen("xor.in","r",stdin);
//	freopen("xor.out","w",stdout);
	n = Read();
	for(int i = 1,u,v;i < n;++ i)
	{
		u = Read()+1,v = Read()+1;
		Add_Double_Edge(u,v,Read());
	}
	d[1] = 1,dfs(1);
	for(int i = 1;i <= n;++ i) val[i] = Read(); 
	for(int i = 1;i <= n;++ i) od[i] = f[i] = i,MIN[i] = INF;
	int cnt = n;
	sort(od+1,od+n+1,cmp);
	for(int i = 2;i <= n;++ i)
		if(val[od[i]] == val[od[i-1]])
			unionSet(od[i-1],od[i]),--cnt;
	for(int i = 1;i <= n;++ i)
		if(findSet(i) == i)
			who = i,Add(rt[i],val[i],29),Add(rt[0],val[i],29);
	LL ans = 0;
	while(cnt > 1)
	{
		for(int i = 1;i <= n;++ i)
		{
			who = findSet(i); ori = i;
			Query(rt[0],rt[who],0,29);
		}
		for(int i = 1;i <= n;++ i)
		{
			if(MIN[i] < INF && findSet(i) != findSet(CID[i]))
			{
				unionSet(i,CID[i]);
				ans += MIN[i];
				MIN[i] = INF;
				--cnt;
			}
		}
	}
	Put(ans);
	return 0;
}
原文地址:https://www.cnblogs.com/PPLPPL/p/15244705.html