题目
题意概要
对于简单无向图 G = ( V , E ) G=(V,E) G=(V,E),边有边权 v e v_e ve,请选出 S ⫅ V S\subseteqq V S⫅V,使得下式取最大值:
∑ 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 x∈S∑ax−⟨a,b⟩∈E∑a,b∈Sve
即,当一条边的端点同时被选时,失去这条边的边权;当一个点被选时,获得其点权。
为了减少输入量,采用如下方式生成数据:记 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=(qai−1+b)modp(1⩽i⩽n) 和 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=(qxi−1+b)modp,yi=(qyi−1+b)modp,zi=(qzi−1+b)modp(1⩽i⩽m),表示存在一条连接 ( 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 2m⩽n⩽105 且 0 ⩽ x 0 , y 0 , a 0 , z 0 < p 0\leqslant x_0,y_0,a_0,z_0<p 0⩽x0,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 了几个点,令人痛心。我对不起卷爷。