NOIP0805模拟赛题解

T1.旅行

大致题意:有一个图,第 \(i\) 次,删除第 \(i\) 个点,或者删除第 \(i\) 个点,并将他的所有邻居连起来。

每次询问可以通过一组路径相互到达的结点对数。

对于询问的内容,由于是无向图,只有在同一个连通块中的节点才能互相到达。

假设有 \(t\) 个连通块,设第 \(i\) 个连通块的大小为 \(sz_i\) ,于是一个连通块中满足要求的点对数量有 \(\frac{(sz_i-1)sz_i}{2}\)

于是问题转化成 \(\sum{\frac{(sz_i-1)sz_i}{2}}\)

对于本题的操作,我们应该可以联想到一个经典的问题。

如果将第二种操作去掉,我们只需要将整个过程倒过来,把删点变成加点,用并查集维护即可。

那么我们是否可以用类似的方法处理?

考虑第二个操作,容易发现,这个操作不会影响其所在连通块的连通性,只会使连通块大小 \(-1\)

于是,我们可以认为第二个操作就是将该点所在连通块大小 \(-1\) ,倒过来也就是 \(+1\)

由此,我们可以倒序处理,一开始,只存在要进行操作二的点,他们的连通块大小为0。

倒叙操作时,对于操作一,我们将那个点加入,将连通块大小设置为1,然后加边。

对于操作二,我们将那个点所在的连通块大小加一。

每次连通块大小改变时,减去原来连通块的大小的贡献,加上新的连通块大小的贡献。

对于第一个部分分,考虑每次,对于 \(s_i=0\) ,暴力删除点,以及点周围的边,对于 \(s_i=1\) ,将点的权值置为0(点初始权值为1),每次用dfs计算每个联通块的权值之和。

对于第二个部分分,将整个过程倒过来,每次加入一个点,连上边,用并查集统计每个区块的大小即可。

T2.造山

大致题意:对于一个序列,对于每个元素每次操作可以 \(+1\)\(-1\) ,求最少多少次操作可以使得原序列严格上升。

首先,有一个很常见的trick,那就是对于严格上升序列,或者类似的各种严格的大于或者等于这些东西。

我们可以令 \(a_i-=i\) 。这样就将严格的,转换成不严格上升序列。

我们可以观察得出一个结论,修改后的数组的数集,应该是原数组的数集的子集,这个可以用调整法证明。

然后我们对数组离散化,就可以进行dp。

\(f[i][j]\) 表示前 \(i\) 个数,将第 \(i\) 个数修改为 \(j\) 并且满足不严格上升最小需要几次修改。

\(f[i][j] = \min(f[i][j],f[i-1][1\dots j])\)

这是一个 \(O(n^3)\) 的东西。

但发现,只需要对其进行一个前缀min的优化即可到 \(O(n^2)\)

(这题有\(O(n\log n)\)的做法,需要slope trick,可以自行学习)

对于第一个部分分,令 \(a_i\) 变为 \(a_1+abs(i-n/2+1)\) (此处 \(a_1\) 是原来的 \(a_1\)

对于第二个部分分,令 \(a_i\)\(i\) ,变为求不下降的最小方案数,于是所有数变为同一个,全部变为 \(a_{\frac{n+1}{2}}\) 算一下就行。

T3.星际大战

大致题意:在强联通有向图中,是否存在超过 \(20\%\) 的到其他任意节点的简单路径唯一的点,如果超过,那么输出有哪些点

首先,考虑如何进行暴力,20分只需要枚举起点,然后用一个状压dp计算他到其他点的简短路径的数量。

考虑如何优化,我们的算法瓶颈在于判断一个点是否危险。

容易想到(出题人真的想到了),可以用dfs树来判断。

具体地,如果以一个点为根,建立dfs树,如果没有横叉边和前向边,那么这个点是危险的。

由此我们可以 \(O(n)\) 判断一个点是否是危险的,总体上 \(O(n^2)\) 可以完成。

观察题目,注意到 \(20\%\) 这个神秘东西。

发现,我们可以随机一个点,判断他是否危险。

随机 \(t\) 次,当危险点的数量超过 \(20\%\) ,找不到的概率为 \(0.8^t\)\(t\) 较大时(比如100),这个概率很小。

于是,我们可以用 \(O(100n)\) 的复杂度找到一个危险的点。

接下来,我们要考虑怎么用一个危险的点算出其他所有危险的点。

对于 \(v\) 点,当且仅当 \(v\) 的子树中有且仅有一条连向 \(v\) 祖先的返祖边,并且其连向的点 %u% 是危险的点。此时 \(v\) 也是危险的点。

我们只要对于每条 \(u\)\(v\) 的返祖边,将 \(u\)\(v\) 的点的权值加一(可以用树上差分做),即可解决第一个限制"当且仅当 \(v\) 的子树中有且仅有一条连向 \(v\) 祖先的返祖边"

接着,我们进行dfs,对于每个权值为1的点,其是否是危险的点,等价于,其子树内返祖边到达的点是否是危险的点。

T4.你的世界

考虑分析一个人的行为。一个人可以捡起经过的所有位置上的物,而它经过的所有位置是一段区间。并且,经过了整个区间,当且仅当它经过了区间的左右端点,因此固定区间时一个人的代价很容易计算出来。

考虑分析所有人的行为。通过调整法可以发现,可以认为任意两个人的区间都是不交的。

考虑一个很暴力的转化:把数轴按照人和物初始所在的位置分段,则最优解中每一段中所有边经过的次数相等,且均为 \(0,1,2\) 。因此直接枚举的状态量非常少。

考虑判定一个 \(0,1,2\) 的方案是否合法。考虑找充要条件。列几个条件可以发现,合法当且仅当:

物两侧两条线段不同时为 \(0\) ,不分别为 \(1\)\(2\)

人两侧两条线段不同时为 \(1\) ,不同时为 \(2\)

每个非 \(0\) 的段至少经过一头人。

用这个充要条件容易写出DP。设 \(f_{i,j}\) ( \(j=0 \dots 4\) )

表示:考虑了与前 \(i\) 个边,

\(j=0\):最后一段未经过人,最后一条边权值为 1。
\(j=1\):最后一段未经过人,最后一条边权值为 2。
\(j=2\):最后一段已经过人,最后一条边权值为 1。
\(j=3\):最后一段已经过人,最后一条边权值为 2。
\(j=4\):最后一条边权值为 0。

#include<bits/stdc++.h>
#define ll long long
#define N 400009
#define mp make_pair
#define fi first
#define se second
using namespace std;
ll f[N][5];
pair<ll,bool> pos[N];
int n,m,tot;
ll M;
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>M>>n>>m;
	for(int i=1;i<=n;i++){
		ll x,y;
		cin>>x>>y;
		for(ll j=x;j<=y;j+=M)pos[++tot]=mp(j,1);
	}
	for(int i=1;i<=m;i++){
		ll x,y;
		cin>>x>>y;
		for(ll j=x;j<=y;j+=M)pos[++tot]=mp(j,0);
	}
	sort(pos+1,pos+tot+1);
	memset(f,0x3f,sizeof(f));
	if(pos[1].se)f[1][3]=f[1][4]=0;else f[1][0]=0;;
	for(int i=2;i<=tot;i++){
		ll w=pos[i].fi-pos[i-1].fi;
		if(!pos[i].se){
			f[i][0]=min(f[i-1][3],f[i-1][4]);
			f[i][1]=min(f[i-1][1],f[i-1][0])+w;
			f[i][2]=min(f[i-1][2],f[i-1][0])+(w<<1);
			f[i][3]=f[i-1][3]+w;
			f[i][4]=f[i-1][4]+(w<<1);
		}
		else{
			f[i][3]=min(min(f[i-1][2],f[i-1][0])+(w<<1),min(f[i-1][3],f[i-1][4]));
			f[i][4]=min(min(f[i-1][1],f[i-1][0])+w,min(f[i-1][3],f[i-1][4]));
		}
//		for(int j=0;j<=4;j++)cout<<f[i][j]<<" ";cout<<"\n";
	}
	cout<<min(f[tot][3],f[tot][4]);
	return 0;
}

满分做法不要求掌握。

首先可以将这个dp转移的方程改成 \((min,+)\) 矩阵乘法。

于是我们要维护的是 \(1e18\) 个矩阵的乘积。

如果理解矩阵的意义,应该是可以注意到,一个不含人或物端点的区间,每个位置对应的矩阵有一个长为 \(m\) 的周期。

然后就只需要维护每个周期的矩阵,这个可以用线段树来维护,然后在对每个端点维护即可。

就可以获得满分。

#include<bits/stdc++.h>
using namespace std;
#define i128 __int128
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-x))
#define It set<int>::iterator
//#define ls (rt<<1)
//#define rs (rt<<1|1)
//#define mid (l+r>>1)
//#define lson tr[rt].ls
//#define rson tr[rt].rs
//#define ls1 tr[rt1].ls
//#define rs1 tr[rt1].rs
//#define ls2 tr[rt2].ls
//#define rs2 tr[rt2].rs
#define pb emplace_back
const int mod=998244353;
int ksm(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		y>>=1;
	}
	return res;
}
int n,m;
char s[200005];
struct edge{
	int v,nx;
}e[800005];
int cnt,hd[200005];
void add(int u,int v){
	e[++cnt]=edge{v,hd[u]};
	hd[u]=cnt;
}
void add_edge(int u,int v){
	add(u,v);add(v,u);
}
int fa[200005],sz[200005];
bool vis[200005];
ll res=0;
ll calc(int x){
	return (ll)x*(x-1)/2;
}
void init(){
	for(int i=1;i<=n;i++)fa[i]=i;
}
int find(int x){
	if(x==fa[x])return x;
	else return fa[x]=find(fa[x]);
}
void merge(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy)return;
	res-=calc(sz[fx]);res-=calc(sz[fy]);
	res+=calc(sz[fx]+sz[fy]);
	sz[fx]+=sz[fy];fa[fy]=fx;
}
vector<ll> ans;
int main(){
	scanf("%d%d",&n,&m);init();
	scanf("%s",s+1);
	for(int i=1;i<=m;i++){
		int u,v;scanf("%d%d",&u,&v);
		add_edge(u,v);
	}
	for(int u=1;u<=n;u++)
		if(s[u]=='1'){
			vis[u]=1;
			for(int i=hd[u];i;i=e[i].nx){
				int v=e[i].v;
				if(vis[v])merge(u,v);
			}
		}else sz[u]=1;
	for(int u=n;u>=1;u--){
		if(s[u]=='0'){
			vis[u]=1;
			for(int i=hd[u];i;i=e[i].nx){
				int v=e[i].v;
				if(vis[v])merge(u,v);
			}
		}else{
			int fu=find(u);
			res-=calc(sz[fu]);
			sz[fu]++;
			res+=calc(sz[fu]);
		}
		ans.pb(res);
	}
	reverse(ans.begin(),ans.end());
	for(ll as:ans)printf("%lld\n",as);
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n, a[3005];
ll f[3005][3005];
int b[3005], cnt;
map<int, int> p;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]), a[i] -= i, b[i] = a[i];
	sort(b + 1, b + 1 + n);
	for (int i = 1; i <= n; i++)
		if (b[i] != b[i - 1])
			b[++cnt] = b[i], p[b[cnt]] = cnt;
	for (int i = 1; i <= n; i++)
		a[i] = p[a[i]];
	memset(f, 0x3f, sizeof(f));
	for (int i = 0; i <= n; i++)
		f[0][i] = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= cnt; j++)
			f[i][j] = f[i - 1][j] + abs(b[j] - b[a[i]]);
		for (int j = 1; j <= cnt; j++)
			f[i][j] = min(f[i][j], f[i][j - 1]);
	}
	ll res = f[n][1];
	for (int i = 1; i <= cnt; i++)
		res = min(res, f[n][i]);
	printf("%lld\n", res);
	return 0;
}
#include <bits/stdc++.h>
namespace mystd {
	inline int read() {
	    char c = getchar();
	    int x = 0, f = 1;
	    while (c < '0' || c > '9') f = (c == '-') ? -1 : f, c = getchar();
	    while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0', c = getchar();
	    return x * f;
	}
	inline void write(int x) {
	    if (x < 0) x = ~(x - 1), putchar('-');
	    if (x > 9) write(x / 10);
	    putchar(x % 10 + '0');
	}
}
using namespace std;
using namespace mystd;

const int maxn = 1e5 + 1000;
const int maxm = 3e5 + 2000;
struct edge { int to, nxt; } e[maxm];
int T, n, m, tot, fl, head[maxn], in[maxn], vis[maxn], cnt[maxn], pr[maxn], dep[maxn];
vector<int> g[maxn], tr[maxn], ans, tp;

void add_edge(int u, int v) {
	e[++tot] = (edge) { v, head[u] };
	head[u] = tot;
}

void dfs(int u) {
	in[u] = 1, vis[u] = 1;
	for (int i = head[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (vis[v] && !in[v]) fl = 1;
		else if (!in[v]) dep[v] = dep[u] + 1, dfs(v), tr[u].push_back(v);
		else g[u].push_back(v), pr[u] = (dep[v] > dep[pr[u]]) ? pr[u] : v;
	}
	in[u] = 0;
}

bool check(int u) {
	for (int i = 1; i <= n; i++) g[i].clear(), tr[i].clear(), cnt[i] = vis[i] = in[i] = pr[i] = 0;
	fl = 0, dep[u] = 1, dep[0] = n + 1;
	dfs(u);
	return !fl;
}

void calc(int u) {
	for (int v : tr[u]) {
		calc(v), cnt[u] += cnt[v];
		if (dep[pr[u]] > dep[pr[v]]) pr[u] = pr[v];		
	}
}

void solv(int u) {
	if (cnt[u] == 1 && dep[pr[u]] < dep[u] && vis[pr[u]]) vis[u] = 1;
	for (int v : tr[u]) solv(v); 
}

void solve() {
	n = read(), m = read(), tot = 0;
	for (int i = 1; i <= n; i++) cnt[i] = head[i] = 0;
	ans.clear(), tp.clear();
	for (int i = 1; i <= n; i++) tp.push_back(i);
	for (int i = 1, u, v; i <= m; i++) {
		u = read(), v = read();
		add_edge(u, v);
	}
	int rt = -1;
	random_shuffle(tp.begin(), tp.end());
	for (int i = 0; i <= tp.size() - 1 && i < 100; i++) {
		if (check(tp[i])) {
			rt = tp[i];
			break;
		}
	}
	if (rt == -1) return puts("YES"), void();
	for (int u = 1; u <= n; u++) for (int v : g[u]) cnt[u]++, cnt[v]--;
	calc(rt);
	for (int i = 1; i <= n; i++) vis[i] = 0;
	vis[rt] = 1;
	solv(rt);
	for (int i = 1; i <= n; i++) if (vis[i]) ans.push_back(i);
	if ((int)ans.size() * 5 < n) puts("YES");
	else{
		puts("NO");
		for (int i : ans) write(i), putchar(' ');
		puts("");
	}
}

int main() {
	srand((unsigned)time(0));
	T = read();
	while (T--) solve();
	return 0;
}
#include<bits/stdc++.h>
#define ll long long
#define N 400009
#define mp make_pair
#define fi first
#define se second
using namespace std;
ll f[N][5];
pair<ll,bool> pos[N];
int n,m,tot;
ll M;
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>M>>n>>m;
	for(int i=1;i<=n;i++){
		ll x,y;
		cin>>x>>y;
		for(ll j=x;j<=y;j+=M)pos[++tot]=mp(j,1);
	}
	for(int i=1;i<=m;i++){
		ll x,y;
		cin>>x>>y;
		for(ll j=x;j<=y;j+=M)pos[++tot]=mp(j,0);
	}
	sort(pos+1,pos+tot+1);
	memset(f,0x3f,sizeof(f));
	if(pos[1].se)f[1][3]=f[1][4]=0;else f[1][0]=0;;
	for(int i=2;i<=tot;i++){
		ll w=pos[i].fi-pos[i-1].fi;
		if(!pos[i].se){
			f[i][0]=min(f[i-1][3],f[i-1][4]);
			f[i][1]=min(f[i-1][1],f[i-1][0])+w;
			f[i][2]=min(f[i-1][2],f[i-1][0])+(w<<1);
			f[i][3]=f[i-1][3]+w;
			f[i][4]=f[i-1][4]+(w<<1);
		}
		else{
			f[i][3]=min(min(f[i-1][2],f[i-1][0])+(w<<1),min(f[i-1][3],f[i-1][4]));
			f[i][4]=min(min(f[i-1][1],f[i-1][0])+w,min(f[i-1][3],f[i-1][4]));
		}
//		for(int j=0;j<=4;j++)cout<<f[i][j]<<" ";cout<<"\n";
	}
	cout<<min(f[tot][3],f[tot][4]);
	return 0;
}
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3fll
#define N 40009
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
struct mat{
	ll a[5][5];
	void init(){memset(a,0x3f,sizeof(a));}
	mat operator*(const mat b){
		mat ret;ret.init();
		for(int i=0;i<5;i++){
			for(int j=0;j<5;j++){
				for(int k=0;k<5;k++)ret.a[i][k]=min(ret.a[i][k],a[i][j]+b.a[j][k]);
			}
		}
		return ret;
	}
} tr[N<<2];
mat get(int x,ll y){
	mat ret;ret.init();
	if(x==2){
		for(int i=0;i<=4;i++)ret.a[i][i]=0;
	}
	else if(x==1){
		ret.a[3][0]=y<<1,ret.a[3][2]=y<<1,ret.a[4][0]=y,ret.a[4][1]=y;
		ret.a[3][3]=0,ret.a[3][4]=0,ret.a[4][3]=0,ret.a[4][4]=0;
	}
	if(!x){
		ret.a[0][3]=ret.a[0][4]=0;
		ret.a[1][0]=ret.a[1][1]=y;
		ret.a[2][0]=ret.a[2][2]=y<<1;
		ret.a[3][3]=y;
		ret.a[4][4]=y<<1;
	}
	return ret;
}
inline void up(int rt){tr[rt]=tr[rs]*tr[ls];}
void build(int rt,int l,int r){
	tr[rt]=get(2,0);
	if(l!=r){
		int mid=(l+r>>1);
		build(ls,l,mid);
		build(rs,mid+1,r);
	}
}
void upd(int rt,int l,int r,int x,mat v){
	if(l==r)tr[rt]=v;
	else{
		int mid=(l+r>>1);
		if(x<=mid)upd(ls,l,mid,x,v);
		else upd(rs,mid+1,r,x,v);
		up(rt);
	}
}
mat qry(int rt,int l,int r,int ql,int qr){
	if(ql>r||qr<l)return get(2,0);
	else if(ql<=l&&qr>=r)return tr[rt];
	else{
		int mid=(l+r>>1);
		return qry(rs,mid+1,r,ql,qr)*qry(ls,l,mid,ql,qr);
	}
}
ll M;int n,m,tot;
struct node1{
	int typ;
	ll l,r;
	bool operator<(const node1 &b){
		return l%M<b.l%M;
	}
} a[N];
struct node2{
	int id,typ;
	ll pos;
	bool operator<(const node2 &b){
		return pos<b.pos||(pos==b.pos&&id<b.id);
	}
} b[N<<1];
set<int> st;
mat qpow(mat x,ll y){
	mat z=get(2,0);
	while(y){
		if(y&1)z=z*x;
		x=x*x;y>>=1;
	}
	return z;
}
ll ps[N];
ll d[N];int tp[N];
bool is[N];
mat sum(ll x,int idx,ll y,int idy){
	if(x>y||x==y&&idx>idy)return get(2,0);
	if(x==y){
		return qry(1,1,n,idx,idy);
	}
	else if(x==y-1){
		return qry(1,1,n,1,idy)*qry(1,1,n,idx,n);
	}
	else{
		return qry(1,1,n,1,idy)*qpow(tr[1],y-x-1)*qry(1,1,n,idx,n);
	}
}
inline void findnxt(ll &x,int &y){
	if(y==n){++x,y=1;}
	else ++y;
}
inline void findlst(ll &x,int &y){
	if(y==1){--x,y=n;}
	else --y;
}
inline ll getpos(ll x,int y){
	return x*M+ps[y];
}
ll ans[5]={INF,INF,INF,INF,INF};
void add(mat tmp){
	for(int j=0;j<5;j++){
		for(int k=0;k<5;k++){
			tmp.a[j][k]+=ans[k];
		}
	}
	for(int j=0;j<5;j++){
		ans[j]=INF;
		for(int k=0;k<5;k++){
			ans[j]=min(ans[j],tmp.a[j][k]);
		}
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>M>>n>>m;
	for(int i=1;i<=n;i++){
		ll x,y;cin>>x>>y;
		a[++tot]=node1{1,x,y};
	}
	for(int i=1;i<=m;i++){
		ll x,y;cin>>x>>y;
		a[++tot]=node1{0,x,y};
	}sort(a+1,a+tot+1);
	for(int i=1;i<=tot;i++){
		ps[i]=a[i].l%M;
		b[i*2-1]=node2{i,a[i].typ,a[i].l};
		b[i<<1]=node2{i,a[i].typ,a[i].r+M};
	}n=tot;tot<<=1;sort(b+1,b+tot+1);
	b[0]=b[1];build(1,1,n);ll lst=b[1].pos/M;int lid=b[1].id;
	if(b[1].typ)ans[3]=ans[4]=0;else ans[0]=0;
	st.insert(lid);upd(1,1,n,lid,get(b[1].typ,M));d[lid]=M;
	is[lid]=1;tp[lid]=b[1].typ;
	for(int i=2;i<=tot;i++){
		int nid=b[i].id;ll now=b[i].pos/M;
		findlst(now,nid);findnxt(lst,lid);
		add(sum(lst,lid,now,nid));findnxt(now,nid);findlst(lst,lid);
		if(is[nid]){
			st.erase(nid);
			upd(1,1,n,nid,get(2,0));
			if(!st.empty()){
				auto it=st.upper_bound(nid);
				if(it==st.end())it=st.begin();
				int t=*it;
				d[t]+=d[nid];
				upd(1,1,n,t,get(tp[t],d[t]));
			}
			d[nid]=tp[nid]=0;
		}
		else{
			d[nid]=ps[nid];tp[nid]=b[i].typ;
			bool flag=0;
			if(!st.empty()){
				auto it=st.lower_bound(nid);
				if(it==st.begin())it=st.end(),d[nid]+=M;--it;
				d[nid]-=ps[*it];
			}else d[nid]=M,flag=1;
			if(!st.empty()){
				auto it=st.upper_bound(nid);
				if(it==st.end())it=st.begin();
				int t=*it;
				d[t]-=d[nid];
				upd(1,1,n,t,get(tp[t],d[t]));
			}
			if(flag)add(get(tp[nid],getpos(now,nid)-getpos(lst,lid)+M));
			else add(get(tp[nid],d[nid]));
			upd(1,1,n,nid,get(tp[nid],d[nid]));
			st.insert(nid);is[nid]=1;
		}
		lid=nid;lst=now;
	}
	cout<<min(ans[3],ans[4])<<"\n";
	return 0;
}
posted @ 2025-08-03 18:08  Kent530  阅读(38)  评论(0)    收藏  举报