Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1257 Accepted Submission(s): 218
题目大意:给你1e5个点带点权的无向图,1e5次操作,要么更改某点的点权,要么查询某个点周围点集的MEX。思路 :把每个点分成两部分,一个是度数 >= sqrt(N)的点,一个是度数 < sqrt(N)的点,前者我称为大堆,后者我称为小堆,小堆暴力,大堆用数据结构维护(分块的基操)。维护MEX,很容易想到权值线段树or树状数组,注意这里用vector动态开二维的; 查询时二分某个位置,看看前面是不是已经填满;为了防止一个集合有多个一样的数,再开一个vector,记录一下每次更新是不是 0,1的分界点即可。
Accepted code
#include
#include
using namespace std;
#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))
typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 1e5 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }
vector G[N], mx[N]; // 存图,大堆点集
int a[N], n, m;
int d[N], limit; // 度数,根号限制
bool vis[350];
void init() {
for (int i = 0; i <= n; i++)
G[i].clear(), mx[i].clear(), d[i] = 0;
}
// 二维树状数组
vector bt[350];
vector nb[350];
int sz[350], idx[N], cnt;
void Add(int x, int y, int w) {
int szz = sz[x];
while (y <= szz) {
bt[x][y] += w;
y += lowbit(y);
}
}
int Ask(int x, int y) {
int tot = 0;
while (y > 0) {
tot += bt[x][y];
y -= lowbit(y);
}
return tot;
}
// 更新大堆
void Calc(int x, int w) {
for (auto v : mx[x]) {
int id = idx[v];
if (a[x] <= d[v]) { // 删旧
nb[id][a[x]]--;
if (!nb[id][a[x]] && a[x])
Add(id, a[x], -1);
}
if (w <= d[v]) { // 加新
nb[id][w]++;
if (nb[id][w] == 1 && w)
Add(id, w, 1);
}
}
a[x] = w;
}
// 查询
int Ask_Big(int x) {
if (!nb[idx[x]][0]) // 0特判
return 0;
int l = 0, r = sz[idx[x]];
int mex = sz[idx[x]];
while (l <= r) {
int mid = (l + r) >> 1;
if (Ask(idx[x], mid) < mid)
mex = mid, r = mid - 1;
else
l = mid + 1;
}
return mex;
}
int Ask_Small(int x) { // 小根暴力
for (int i = 0; i <= d[x]; i++)
vis[i] = false;
for (auto v : G[x])
if (a[v] <= d[x])
vis[a[v]] = true;
for (int i = 0; i <= d[x]; i++) {
if (!vis[i])
return i;
}
}
int main()
{
int T; cin >> T;
while (T--) {
sc("%d %d", &n, &m);
init();
for (int i = 1; i <= n; i++)
sc("%d", &a[i]);
for (int i = 0; i < m; i++) {
int u, v;
sc("%d %d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
d[u]++, d[v]++;
}
// 分块
cnt = 0;
limit = sqrt(n);
for (int i = 1; i <= n; i++) {
if (d[i] < limit)
continue;
idx[i] = ++cnt; // 重新编号
sz[cnt] = d[i]; // 树状数组大小
bt[cnt].resize(d[i] + 5);
nb[cnt].resize(d[i] + 5);
for (int j = 0; j <= d[i]; j++)
bt[cnt][j] = nb[cnt][j] = 0; // 清空
for (auto v : G[i]) { // 该位置的数量
if (a[v] > d[i]) // 超过度数不加
continue;
nb[cnt][a[v]]++;
if (nb[cnt][a[v]] == 1 && a[v]) // 01分界点
Add(cnt, a[v], 1);
}
}
for (int i = 1; i <= n; i++) { // 保存大堆
for (auto v : G[i]) {
if (d[v] >= limit)
mx[i].push_back(v);
}
}
int q;
sc("%d", &q);
while (q--) {
int op, x, w;
sc("%d %d", &op, &x);
if (op == 1) {
sc("%d", &w);
Calc(x, w);
}
else {
if (d[x] >= limit)
printf("%d\n", Ask_Big(x));
else
printf("%d\n", Ask_Small(x));
}
}
}
return 0; // 改数组大小!!!用pair记得改宏定义!!!
}