[太阳神]挑战NPC

题目

题意概要
对于简单无向图 G = ( V , E ) G=(V,E) G=(V,E),边有边权 v e v_e ve,请选出 S ⫅ V S\subseteqq V SV,使得下式取最大值:
∑ x ∈ S a x − ∑ ⟨ a , b ⟩ ∈ E a , b ∈ S v e \sum_{x\in S}a_x-\sum_{\langle a,b\rangle\in E}^{a,b\in S}v_e xSaxa,bEa,bSve
即,当一条边的端点同时被选时,失去这条边的边权;当一个点被选时,获得其点权。

为了减少输入量,采用如下方式生成数据:记 q = 101 ,    b = 137 ,    p = 1 0 9 + 7 q=101,\; b=137,\; p=10^9+7 q=101,b=137,p=109+7,读入 x 0 , y 0 , a 0 , z 0 x_0,y_0,a_0,z_0 x0,y0,a0,z0 后,有 a i = ( q a i − 1 + b )   m o d   p    ( 1 ⩽ i ⩽ n ) a_i=(qa_{i-1}+b)\bmod p\;(1\leqslant i\leqslant n) ai=(qai1+b)modp(1in) x i = ( q x i − 1 + b )   m o d   p ,    y i = ( q y i − 1 + b )   m o d   p ,    z i = ( q z i − 1 + b )   m o d   p    ( 1 ⩽ i ⩽ m ) x_i=(qx_{i-1}+b)\bmod p,\;y_i=(qy_{i-1}+b)\bmod p,\;z_i=(qz_{i-1}+b)\bmod p\;(1\leqslant i\leqslant m) xi=(qxi1+b)modp,yi=(qyi1+b)modp,zi=(qzi1+b)modp(1im),表示存在一条连接 ( x i   m o d   n ) + 1 (x_i\bmod n)+1 (ximodn)+1 ( y i   m o d   n ) + 1 (y_i\bmod n)+1 (yimodn)+1 的边,权值为 z i z_i zi 。特别地,若 x i = y i x_i=y_i xi=yi 或这是一条已经存在的边,则忽略这条边。

数据范围与提示
2 m ⩽ n ⩽ 1 0 5 2m\leqslant n\leqslant 10^5 2mn105 0 ⩽ x 0 , y 0 , a 0 , z 0 < p 0\leqslant x_0,y_0,a_0,z_0<p 0x0,y0,a0,z0<p

思路

显然这个问题不弱于最大独立集,而那是 n p c \rm npc npc 问题。应该不至于这么早出名吧

而图是随机的,边又特别少,这导向乱搞做法。比如,有一些点是必然会被选择的:它的邻边的边权和不超过它的点权。选择它之后,将其从图中删去,然后考虑每一条邻边,发现边权变成了点权!因为它的邻边的边权是否会失去,只跟邻接点的状态有关。所以将邻接点的点权作对应修改,就得到了一个递归子问题。

那么,点权非正时,必然不选。所以也可以删去。除去上面这两种点,仍然剩很多点;注意我们还没用到图非常稀疏的特性!于是考察一下度数为 1 1 1 的点:它的边权是大于点权的(否则是上面的第一种点)。也就是说,它的邻点一旦选了,它必然不选;反之,它的邻点不选,则它必选。利用上面递归的思想,先将它的点权加入答案,然后将其邻点的点权减去它的点权,表示 “选了邻点则不选它”。所以度数为 1 1 1 的点也被删掉啦!

剩下的咋办?指数级暴力。事实上,对于搬题人造的数据,一个点也不会剩下。于是我造了很多数据(不断随机,找其中可以剩下点的),最坏的一组也只剩下了 10 10 10 个点……

所以复杂度近乎 O ( n + m ) \mathcal O(n+m) O(n+m)

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <set>
#include <queue>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
    
    
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int __q = 101, __b = 137, __p = 1e9+7;
const int MAXN = 100005;
struct Edge{
    
    
	int to, nxt, val;
	Edge() = default;
	Edge(int _to,int _nxt,int _val):
		to(_to),nxt(_nxt),val(_val){
    
     }
};
Edge e[MAXN];
int head[MAXN], cntEdge;
void addEdge(int a,int b,int c){
    
    
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
}

llong sum[MAXN], a[MAXN];
bool dead[MAXN]; ///< if it's decided

int sta[MAXN], top, dfn[MAXN];
void bfs(int x){
    
    
	sta[++ top] = x, dfn[x] = top;
	for(int i=head[x]; ~i; i=e[i].nxt){
    
    
		if(dead[e[i].to]) continue;
		if(!dfn[e[i].to]) bfs(e[i].to);
		if(dfn[e[i].to] > dfn[x])
			sum[e[i].to] -= e[i].val;
	}
}

llong best_val;
void dfs(int x,const llong &now_val){
    
    
	if(x == top+1){
    
    
		if(best_val < now_val)
			best_val = now_val;
		return ; // the end
	}
	// if this one not chosen
	if(sum[sta[x]] > a[sta[x]])
		dfs(x+1,now_val);
	// if this one is chosen
	if(a[sta[x]] > 0){
    
    
		for(int i=head[sta[x]]; ~i; i=e[i].nxt)
			if(!dead[e[i].to] && dfn[e[i].to] > x)
				a[e[i].to] -= e[i].val;
		dfs(x+1,now_val+a[sta[x]]);
		for(int i=head[sta[x]]; ~i; i=e[i].nxt)
			if(!dead[e[i].to] && dfn[e[i].to] > x)
				a[e[i].to] += e[i].val;
	}
}

int deg[MAXN];
int main(){
    
    
	int n = readint(), m = readint();
	memset(head+1,-1,n<<2);
	{
    
     // input
		set< std::pair<int,int> > s;
		int x0 = readint(), y0 = readint();
		a[0] = readint(); int z0 = readint();
		rep(i,1,n) a[i] = (__q*a[i-1]+__b)%__p;
		for(int i=1,x,y; i<=m; ++i){
    
    
			x0 = int((llong(__q)*x0+__b)%__p);
			y0 = int((llong(__q)*y0+__b)%__p);
			z0 = int((llong(__q)*z0+__b)%__p);
			x = x0%n+1, y = y0%n+1;
			if(x == y || s.count(make_pair(x,y))
			|| s.count(make_pair(y,x))) continue;
			s.insert(std::make_pair(x,y));
			addEdge(x,y,z0), addEdge(y,x,z0);
			sum[x] += z0, sum[y] += z0;
			++ deg[x], ++ deg[y]; // to delete leaf
		}
	}
	llong ans = 0;
	{
    
     // cut branches
		std::queue<int> q;
		rep(i,1,n) q.push(i);
		while(!q.empty()){
    
    
			int x = q.front(); q.pop();
			if(dead[x]) continue;
			if(sum[x] <= a[x]){
    
     // always chosen
				dead[x] = true; ans += a[x];
				for(int i=head[x]; ~i; i=e[i].nxt)
					if(!dead[e[i].to]){
    
    
						sum[e[i].to] -= e[i].val;
						a[e[i].to] -= e[i].val; // both chosen
						-- deg[e[i].to];
						q.push(e[i].to); // re-check
					}
			}
			else if(a[x] <= 0){
    
     // never chosen
				dead[x] = true;
				for(int i=head[x]; ~i; i=e[i].nxt)
					if(!dead[e[i].to]){
    
    
						sum[e[i].to] -= e[i].val;
						-- deg[e[i].to];
						q.push(e[i].to); // re-check
					}
			}
			// deg = 0 meaning sum = 0, determined
			else if(deg[x] == 1){
    
    
				ans += a[x]; dead[x] = true;
				for(int i=head[x]; ~i; i=e[i].nxt)
					if(!dead[e[i].to]){
    
    
						sum[e[i].to] -= e[i].val;
						a[e[i].to] -= a[x];
						-- deg[e[i].to];
						q.push(e[i].to); break;
					}
			}
		}
	}
	for(int i=1; i<=n; ++i)
		if(!dead[i] && !dfn[i]){
    
    
			top = 0, bfs(i), best_val = 0;
			dfs(1,0); ans += best_val;
		}
	printf("%lld\n",ans);
	return 0;
}

后记

我造了所谓 h a c k \rm hack hack 数据之后,把它加了上去,把所有人重测了一下,发现都能过。

结果 O U Y E \sf OUYE OUYE 突然 w a \rm wa wa 了几个点,令人痛心。我对不起卷爷。

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/121386264