NOI.ac2020省选模拟赛4
A.背包
problem
有\(n\)个物品,价值都小于等于13,Q次操作,每次操作给出\(l,r\),问区间\([l,r]\)内可以取任意多个物品,对于\(k=0,1,2,...12\)求出价值和\(m\% 13=k\)的方案数对2取模。
然后将第l个物品和第i个物品价值和加1.
\(n\le 8\times 10^6,Q\le 10^5\)
solution
丧心病狂的卡空间!
结束前十分钟修改题意,我已经溜了。。\(90\rightarrow 30\)
用线段树维护,合并两个节点的复杂度是12,然后单点修改合并一下背包就行了。
使用\(short\ int\)存答案可以节省一半空间。
code
/*
* @Author: wxyww
* @Date: 2020-06-04 07:59:47
* @Last Modified time: 2020-06-04 18:50:25
*/
#pragma GCC diagnostic error "-std=c++11"
#pragma GCC optimize("-fdelete-null-pointer-checks,inline-functions-called-once,-funsafe-loop-optimizations,-fexpensive-optimizations,-foptimize-sibling-calls,-ftree-switch-conversion,-finline-small-functions,inline-small-functions,-frerun-cse-after-loop,-fhoist-adjacent-loads,-findirect-inlining,-freorder-functions,no-stack-protector,-fpartial-inlining,-fsched-interblock,-fcse-follow-jumps,-fcse-skip-blocks,-falign-functions,-fstrict-overflow,-fstrict-aliasing,-fschedule-insns2,-ftree-tail-merge,inline-functions,-fschedule-insns,-freorder-blocks,-fwhole-program,-funroll-loops,-fthread-jumps,-fcrossjumping,-fcaller-saves,-fdevirtualize,-falign-labels,-falign-loops,-falign-jumps,unroll-loops,-fsched-spec,-ffast-math,Ofast,inline,-fgcse,-fgcse-lm,-fipa-sra,-ftree-pre,-ftree-vrp,-fpeephole2",3)
#pragma GCC target("avx","sse2")
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 4000010;
ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1; c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar();
}
return x * f;
}
int n,Q;
namespace BF1 {
short int tree[N * 3],cnt[1 << 14];
short int rev[1 << 13][14];
char s[N];
int up(short int x,short int y) {
int ret = 0;
for(int i = 1;i <= 13;++i) {
short int k = y & ((1 << i) - 1);
short int tmp = x & rev[k][i];
ret ^= cnt[tmp] << (i - 1);
}
for(int i = 1;i < 13;++i) {
short int t1 = x >> i,t2 = y >> i;
short int k = rev[t2][13 - i] & t1;
ret ^= cnt[k] << (i - 1);
}
return ret;
}
void build(int rt,int l,int r) {
if(l == r) {
short int x;
if(s[l] > '9') {
if(s[l] == 'a') x = 10;
if(s[l] == 'b') x = 11;
if(s[l] == 'c') x = 12;
}
else x = s[l] - '0';
tree[rt] = 1 << x | 1;
return;
}
int mid = (l + r) >> 1;
build(rt << 1,l,mid);
build(rt << 1 | 1,mid + 1,r);
tree[rt] = up(tree[rt << 1],tree[rt << 1 | 1]);
}
short int num[1 << 13];
void update(int rt,int l,int r,int pos) {
if(l == r) {
short int k = num[tree[rt]];
++k;if(k > 12) k = 1;
tree[rt] = 1 << k | 1;
return;
}
int mid = (l + r) >> 1;
if(pos <= mid) update(rt << 1,l,mid,pos);
else update(rt << 1 | 1,mid + 1,r,pos);
tree[rt] = up(tree[rt << 1],tree[rt << 1 | 1]);
}
int query(int rt,int l,int r,int L,int R) {
if(L <= l && R >= r) {
return tree[rt];
}
int mid = (l + r) >> 1;
int ret = 1;
if(L <= mid) ret = up(ret,query(rt << 1,l,mid,L,R));
if(R > mid) ret = up(ret,query(rt << 1 | 1,mid + 1,r,L,R));
return ret;
}
char t[1 << 13][15];
void print(int x) {
printf("%s\n",t[x]);
}
/*
int up1(int x,int y) {
int ret = 0;
for(int i = 0;i <= 12;++i) {
if(!(x >> i & 1)) continue;
for(int j = 0;j <= 12;++j) {
if(!(y >> j & 1)) continue;
ret ^= 1 << ((i + j) % 13);
}
}
return ret;
}*/
void main() {
scanf("%s",s + 1);
for(int i = 1;i <= 12;++i)
num[1 << i | 1] = i;
for(int j = 1;j <= 13;++j)
for(int i = 0;i < 1 << j;++i)
rev[i][j] = (rev[i >> 1][j] >> 1)| ((i & 1 ? 1 : 0) << (j - 1));
for(int j = 0;j < 1 << 13;++j)
cnt[j] = (cnt[j >> 1] + (j & 1)) & 1;
build(1,1,n);
for(int i = 0;i < 1 << 13;++i) {
for(int j = 0;j <= 12;++j) {
if(i >> j & 1) t[i][j] = '1';
else t[i][j] = '0';
}
}
while(Q--) {
int l = read(),r = read();
// puts("!!");
print(query(1,1,n,l,r));
update(1,1,n,l);
update(1,1,n,r);
}
}
}
int main() {
// freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
n = read(),Q = read();
BF1::main();
return 0;
}
/*
10 10
a2a7b44c48
1 1
6 9
4 10
9 10
8 9
3 5
9 9
1 7
2 5
2 4
*/
B.D
problem
给出n个闭区间,对他们进行黑白染色,使得覆盖每个位置的黑色和白色线段一样多。
\(n\le 10^5\)
solution
我们把黑的看成 \(+1\),白的看成 \(-1\)。对于区间 \([l,r]\),做个差分,便可以看做是一条从 \(l\) 到 \(r+1\) 的边。
我们想要的是每个位置是 \(0, \pm 1\),这个对应到差分上值有 \(0, \pm 1, \pm 2\),不太好处理。但是如果我们想要的是每个位置都是 \(0\),那么差分后每个位置都是 \(0\),也就是说如果建出来的图存在欧拉回路,那么我们按照欧拉回路上的边的方向就可以得到一组解。
对于原题,可以发现为 \(\pm 1\) 的一定是被覆盖奇数次的位置,那么新增一些区间来覆盖它们,使得每个位置都为偶数次。这个时候建出来的图也一定是存在欧拉回路的,于是我们就得到了一组满足每个位置都是 \(0, \pm 1\) 的解。
code
/*
* @Author: wxyww
* @Date: 2020-06-04 11:43:49
* @Last Modified time: 2020-06-04 20:25:43
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1; c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar();
}
return x * f;
}
int n;
namespace BF1 {
const int N = 50;
int ls[N],sum[N];
int l[N],r[N],m = 0;
void main() {
for(int i = 1;i <= n;++i) {
ls[++m] = l[i] = read();
ls[++m] = r[i] = read();
}
sort(ls + 1,ls + m + 1);
int t = 0;
ls[++t] = ls[1];
for(int i = 2;i <= m;++i)
if(ls[i] != ls[i - 1]) ls[++t] = ls[i];
m = t;
for(int i = 1;i <= n;++i) {
l[i] = lower_bound(ls + 1,ls + m + 1,l[i]) - ls;
r[i] = lower_bound(ls + 1,ls + m + 1,r[i]) - ls;
}
for(int i = 0;i < 1 << n;++i) {
memset(sum,0,sizeof(sum));
for(int j = 0;j < n;++j) {
if(i >> j & 1) {
sum[l[j + 1]]++;
sum[r[j + 1] + 1]--;
}
else {
sum[l[j + 1]]--;
sum[r[j + 1] + 1]++;
}
}
int flag = 0;
for(int j = 1;j <= t;++j) {
sum[j] += sum[j - 1];
if(abs(sum[j]) > 1) {
flag = 1;
break;
}
}
if(!flag) {
for(int j = 0;j < n;++j) {
printf("%d ",i >> j & 1);
}
return;
}
}
}
}
namespace BF2 {
const int N = 400010;
#define pi pair<int,int>
vector<pi>a;
struct node {
int v,nxt,id,vis;
}e[N << 1];
int head[N],ejs = 1;
void add(int u,int v,int id) {
// printf("%d %d %d\n",u,v,id);
e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;e[ejs].id = id;
e[++ejs].v = u;e[ejs].nxt = head[v];head[v] = ejs;e[ejs].id = id;
}
int vis[N],col[N];
void dfs(int u) {
vis[u] = 1;
for(int &i = head[u];i;i = e[i].nxt) {
int v = e[i].v;
if(e[i].vis) continue;
e[i].vis = e[i ^ 1].vis = 1;
col[e[i].id] = i & 1;
dfs(v);
}
}
void main() {
for(int i = 1;i <= n;++i) {
int l = read(),r = read();
a.push_back(make_pair(2 * l,i << 1));
a.push_back(make_pair(2 * r + 1,i << 1 | 1));
add(i << 1,i << 1 | 1,i);
}
sort(a.begin(),a.end());
for(int i = 1;i < n + n;i += 2) {
add(a[i - 1].second,a[i].second,0);
}
for(int i = 2;i <= (n << 1 | 1);++i) {
if(!vis[i]) {
dfs(i);
}
}
// puts("!!!");
for(int i = 1;i <= n;++i) printf("%d ",col[i]);
}
}
int main() {
n = read();
// if(n <= 20) {
// BF1::main();return 0;
// }
BF2::main();
return 0;
}
C.魔塔
problem
有一棵\(n\)个节点的树,第一次走到第\(i\)个节点可以获得\(x_i\)的生命值,从\(i\)的父亲走到\(i\)需要\(y_i\)的生命值。
从0号节点开始走到某个叶子节点,中间生命值不能小于0。问初始生命值最少是多少。
\(n\le 5\times 10^5\)
solution
可以发现,对于一个不在入口到出口路径上的点,我们进入它所在的子树仅当我们进入它能赚到生命值。
记\(f(i)\)为从\(i\)号点的父亲进入\(i\)号点所在的子树,赚取一定的生命值(可能是0)所需要的最小生命值。
可以发现,如果我们走到过\(p_i\),并且生命值至少有\(f(i)\),则我们一定会进入\(i\)号点的子树并赚取这些生命值。
我们从后往前依次计算\(f\)。假设当前我们在某个点上,生命值为\(0\)。每次我们找出它的后代中能直接到达的\(f\)最小的点,并且进入这个后代。如果当前的生命值不足以进入这个后代,那么我们就需要追加初始生命值。在进入了某个后代后,这个后代的后代也可以直接到达了,我们需要将这些点加到当前能直接到达的后代列表里。因此,我们可以用可并堆维护所有能直接到达的后代的\(f\)。
为了简单起见,我们可以假设出口有一个容量为无穷大的恢复药水,然后计算从根节点出发得到无穷大的一半的生命值所需的最小初始生命值。
code
/*
* @Author: wxyww
* @Date: 2020-06-04 11:36:24
* @Last Modified time: 2020-06-04 22:07:44
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 500010;
ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1; c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar();
}
return x * f;
}
int p[N],x[N],y[N];
struct node {
int x,y,id;
};
int fa[N],vis[N];
priority_queue<node>q;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool operator < (const node &A,const node &B) {
if(A.x <= A.y && B.x > B.y) return true;
if(A.x > A.y && B.x <= B.y) return false;
if(A.x > A.y) return A.y > B.y;
return A.x < B.x;
}
int main() {
int n = read();
fa[1] = 1;
for(int i = 2;i <= n;++i) {
p[i] = read() + 1;
x[i] = read(),y[i] = read();
if(x[i] == -1) x[i] = 1e9;
q.push((node){x[i],y[i],i});
fa[i] = i;
}
while(!q.empty()) {
node now = q.top();q.pop();
// printf("%d\n",now.id);
if(vis[now.id] || x[now.id] != now.x || y[now.id] != now.y) continue;
vis[now.id] = 1;
int father = find(p[now.id]);
if(x[father] < now.y) {
y[father] += now.y - x[father];
x[father] = now.x;
}
else
x[father] += now.x - now.y;
if(father != 1) q.push((node){x[father],y[father],father});
fa[now.id] = father;
}
cout<<y[1]<<endl;
return 0;
}