Danil and a Part-time Job

题目大意

给一个多叉树,根节点序号为1,树上每一个点是1或者0。

操作,对 一个树的节点以及这个节点的所有子节点进行 零 一 翻转(布尔非)。

询问,求 一个树的节点以及这个节点的所有子节点的 1的个数总和

输入

树上的连接情况也就是边,输入每个点的初始状态

接下来是输入query,为操作或者询问

数据范围

树的点的个数<=200 000

query数量<=200 000

上限 2秒 256MB

解答思路

官方给的是Euler-tour-tree+Segment tree 具体讲解见下面的参考,很清晰了。下面的链接中介绍了三种ETT,这里用得是第二种

思路是首先对树 进行Euler-tour-tree展开,这样操作后上面的操作和询问 都变成了 展开后的区间操作和询问了。

这样变成区间后就可以用segment tree 来做了,加个lazytag减少点时间,目测不加lazy过不了,因为每次对整个操作区间的话,每次改动就是2n

代码

C++14/390ms/40 392KB

用了一下C++14的一些语法[虽然我记不清哪些在11就已经支持了]

实现

也是很久没有写过线段树 更不要说lazytag,上一次写应该是四五年前。

感受0. 既可以向下面这样用线段树的节点来记录 表示的l和r。也可完全不记录,这样通过固定的函数之间递归调用,多传两个参数也可以,目前用前一种方法写下来就是有点乱的感觉,如果每次传参,脑补代码会看上去更简洁。

感受1. lazytag我最开始写的是再调用处理函数,但是最后改成了只对下一层改动,因为lazytag能有tag都是说明到这层已经是和这层的左右范围完全对应,所以向下也是完全对应,所以下层也就不会再往下传,所以直接改当前层和下一层就能把lazytag处理了。

实现0. 这里线段树我的节点上有一个switched来表示是否切换,所以如果一个节点以下的开启的个数,不是直接返回on的值,而是判断一下switched,所以我后面获取on和off见注释fix o下面的。。。没有具体分析这样写是把代码变复杂还是简单还是差不多。因为如果直接表示每次要直接转换,lazytag下发也要再加一些判断。_(:з」∠)_

感受2. 总的来说我也调了半天,代码丑陋写了136行,看rank上排在前面的大佬 90行就写完了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <bits/stdc++.h>

#define NUM 200000+10
#define o_l (o<<1)
#define o_r (o_l+1)

using namespace std;

struct sgtnode{
int l,r;
int swiced;
int on,off;
sgtnode(){swiced=0;on=0;off=0;}
sgtnode(int s,int n,int f):
swiced(s),on(n),off(f){}
};

// --- input ---
int n;
vector<int> child[NUM];
// --- Euler tour tree ---
int ettIndex2ONorOFF[2*NUM];
int ett_l[NUM]={0};
int ett_r[NUM]={0};
// --- Segment tree ---
sgtnode sgmnttr[4*2*NUM];

void lazydown(int o){
if(sgmnttr[o].swiced == 1){
sgmnttr[o].swiced = 0;
swap(sgmnttr[o].on,sgmnttr[o].off);
sgmnttr[o_l].swiced ^=1;
sgmnttr[o_r].swiced ^=1;
}
}
// --- Segment tree switch ---
void segswi(int o,int l,int r){
if(sgmnttr[o].l == l and sgmnttr[o].r == r){
sgmnttr[o].swiced ^= 1;
return ;
}
lazydown(o);
int mid=(sgmnttr[o].l+sgmnttr[o].r)/2;
if(l <= mid)
segswi(o_l,l,min(r,mid));
if(r > mid)
segswi(o_r,max(l,mid+1),r);
// fix o
sgmnttr[o].on =
(sgmnttr[o_l].swiced?sgmnttr[o_l].off:sgmnttr[o_l].on)+
(sgmnttr[o_r].swiced?sgmnttr[o_r].off:sgmnttr[o_r].on);
sgmnttr[o].off =
(sgmnttr[o_l].swiced?sgmnttr[o_l].on:sgmnttr[o_l].off)+
(sgmnttr[o_r].swiced?sgmnttr[o_r].on:sgmnttr[o_r].off);
}
// --- Segment tree query ---
int segqry(int o,int l,int r){
if(sgmnttr[o].l == l and sgmnttr[o].r == r)
return sgmnttr[o].swiced?sgmnttr[o].off:sgmnttr[o].on;
lazydown(o);
int mid=(sgmnttr[o].l+sgmnttr[o].r)/2;
int ret=0;
if(l <= mid)
ret += segqry(o_l,l,min(r,mid));
if(r > mid)
ret += segqry(o_r,max(l,mid+1),r);
return ret;
}

void build_segtree(int o,int l,int r){
sgmnttr[o].l = l;
sgmnttr[o].r = r;
if(l == r){
sgmnttr[o].off = 1^(sgmnttr[o].on = ettIndex2ONorOFF[l]);
return ;
}
int mid =(l+r)/2;
build_segtree(o_l,l,mid);
build_segtree(o_r,mid+1,r);
sgmnttr[o].on = sgmnttr[o_l].on + sgmnttr[o_r].on;
sgmnttr[o].off = sgmnttr[o_l].off + sgmnttr[o_r].off;
}

void qry(int index){
printf("%d\n",segqry(1,ett_l[index],ett_r[index])/2);
}

void swi(int index){
segswi(1,ett_l[index],ett_r[index]);
}

void build_ett(int tree_index){
static int ett_index = 0;
ett_l[tree_index] = ett_index++;
for(auto each_child : child[tree_index])
build_ett(each_child);
ett_r[tree_index] = ett_index++;
}

int main(){
cin>>n;
int i;
for(i=2;i<=n;i++){
int tmp;
scanf("%d",&tmp);
child[tmp].push_back(i);
}
build_ett(1);
// for(i=1;i<=n;i++)
// cout<<ett_l[i]<<" "<<ett_r[i]<<endl;

for(i=1;i<=n;i++){
int tmp;
scanf("%d",&tmp);
ettIndex2ONorOFF[ett_l[i]]=tmp;
ettIndex2ONorOFF[ett_r[i]]=tmp;
}
build_segtree(1,0,2*n-1);
// cout<<"index\tswiced\tl\tr\ton\toff\n";
// for(i=1;i<4*n;i++)
// cout<<i<<"\t"<<sgmnttr[i].swiced<<"\t"<<sgmnttr[i].l<<"\t"<<sgmnttr[i].r<<"\t"<<sgmnttr[i].on<<"\t"<<sgmnttr[i].off<<endl;

int q;
cin>>q;
while(q--){
char s[10];
int tmp;
scanf("%s %d",s,&tmp);
if(s[0]=='g'){
qry(tmp);
}else{
swi(tmp);
}
}
return 0;
}

参考

Problem

Official editorial

Euler tour tree

Segment tree

E. The Untended Antiquity

time limit per test 2 seconds
memory limit per test 512 megabytes
input standard input
output standard output

Koyomi is helping Oshino, an acquaintance of his, to take care of an open space around the abandoned Eikou Cram School building, Oshino’s makeshift residence.

The space is represented by a rectangular grid of n × m cells, arranged into n rows and m columns. The c-th cell in the r-th row is denoted by (r, c).

Oshino places and removes barriers around rectangular areas of cells. Specifically, an action denoted by “1 $r_1$ $c_1$ $r_2$ $c_2$” means Oshino’s placing barriers around a rectangle with two corners being ($r_1$, $c_1$) and ($r_2$, $c_2$) and sides parallel to squares sides. Similarly, “2 $r_1$ $c_1$ $r_2$ $c_2$” means Oshino’s removing barriers around the rectangle. Oshino ensures that no barriers staying on the ground share any common points, nor do they intersect with boundaries of the n × m area.

Sometimes Koyomi tries to walk from one cell to another carefully without striding over barriers, in order to avoid damaging various items on the ground. “3 $r_1$ $c_1$ $r_2$ $c_2$” means that Koyomi tries to walk from ($r_1$, $c_1$) to ($r_2$, $c_2$) without crossing barriers.

And you’re here to tell Koyomi the feasibility of each of his attempts.

Input

The first line of input contains three space-separated integers n, m and q (1 ≤ n, m ≤ 2 500, 1 ≤ q ≤ 100 000) — the number of rows and columns in the grid, and the total number of Oshino and Koyomi’s actions, respectively.

The following q lines each describes an action, containing five space-separated integers t, $r_1$, $c_1$, $r_2$, $c_2$ (1 ≤ t ≤ 3, 1 ≤ $r_1$, $r_2$ ≤ n, 1 ≤ $c_1$, $c_2$ ≤ m) — the type and two coordinates of an action. Additionally, the following holds depending on the value of t:

If t = 1: 2 ≤ $r_1$ ≤ $r_2$ ≤ n - 1, 2 ≤ $c_1$ ≤ $c_2$ ≤ m - 1;
If t = 2: 2 ≤ $r_1$ ≤ $r_2$ ≤ n - 1, 2 ≤ $c_1$ ≤ $c_2$ ≤ m - 1, the specified group of barriers exist on the ground before the removal.
If t = 3: no extra restrictions.

Output

For each of Koyomi’s attempts (actions with t = 3), output one line — containing “Yes” (without quotes) if it’s feasible, and “No” (without quotes) otherwise.

Examples

input

1
2
3
4
5
6
5 6 5
1 2 2 4 5
1 3 3 3 3
3 4 4 1 1
2 2 2 4 5
3 1 1 4 4

output

1
2
No
Yes

input

1
2
3
4
5
6
7
8
9
2500 2500 8
1 549 1279 1263 2189
1 303 795 1888 2432
1 2227 622 2418 1161
3 771 2492 1335 1433
1 2017 2100 2408 2160
3 48 60 798 729
1 347 708 1868 792
3 1940 2080 377 1546

output

1
2
3
No
Yes
No

Solution

二维线段树+set/vector/rand

二维数状数组+rand+’+’

二维数状数组+rand+’^’

官方解答

我读官方解答读下来,发现官方解答用的是一维线段树 只在左右方向进行划分,对于横坐标划分后保存 纵向的 启始和结束以及_id 这样看得话_id是否有点多余??? 但好像写出来比比较 _u_d 看上去简洁 不过这个都不影响复杂度,然后我尝试了把 _u_d进行编码 当作返回,结论是 不可以的,因为 可能目标两个的点所在的矩形 不是同一个,但 这两个矩形的上下的分化是一样的:-)僵硬,结论还是要_id

我反正还没想到2D的线段树也就是 四叉树 加信息标注 要怎么做???? need help 上面三个选手的代码都是带rand()

然后官方题解用了 好多C++新的东西 比如 auto and 低头

以及看别人的代码发现 手工编码解码很少了,大家都用的makepair 甚至两重makepair

F. Yet Another Minimization Problem

time limit per test 2 seconds
memory limit per test 256 megabytes
input standard input
output standard output

You are given an array of n integers $a_1$ ... $a_n$. The cost of a subsegment is the number of unordered pairs of distinct indices within the subsegment that contain equal elements. Split the given array into k non-intersecting non-empty subsegments so that the sum of their costs is minimum possible. Each element should be present in exactly one subsegment.

Input

The first line contains two integers n and k ($2$ ≤ n ≤ $10^5$, 2 ≤ k ≤ min (n, 20)) — the length of the array and the number of segments you need to split the array into.

The next line contains n integers $a_1$, $a_2$, …, $a_n$ (1 ≤ $a_i$ ≤ n) — the elements of the array.

Output

Print single integer: the minimum possible total cost of resulting subsegments.

Examples

input

1
2
7 3
1 1 3 3 3 2 1

output

1
1

input

1
2
10 2
1 2 1 2 1 2 1 2 1 2

output

1
8

input

1
2
13 3
1 2 2 2 1 2 1 1 1 2 2 1 1

output

1
9

Note

In the first example it’s optimal to split the sequence into the following three subsegments: [1], [1, 3], [3, 3, 2, 1]. The costs are 0, 0 and 1, thus the answer is 1.

In the second example it’s optimal to split the sequence in two equal halves. The cost for each half is 4.

In the third example it’s optimal to split the sequence in the following way: [1, 2, 2, 2, 1], [2, 1, 1, 1, 2], [2, 1, 1]. The costs are 4, 4, 1.


Solution

我开始还以为我做出来了,然而第6个点就爆了。下面用|表示分割_表示数字

我的最开始的想法是先随意将原数组随意分割为k组______|____|_________|__________

然后对每一个分割进行左右调整,直到不可调为止,______←|→____|_________|__________,但这样局部最优并不能达到全局最优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
long long costn(long long n){
return (n-1)*n/2;
}
long long cost[100010]={0};
void init(){
int i;
for(i=0;i<=100000;i++)
cost[i]=costn(i);
}

int n,k;
long long ans = 0;

long long v[100010]={0};
long long cnt[100010]={0};
long long cntnow[100010]={0};

int divk[30]={0}; // return index

void clear(){
int i;
for(i=0;i<=n;i++){
cnt[i]=0;
cntnow[i]=0;
}
}

bool doaj(int index){
clear();
int l = index == 0?0:divk[index-1]+1;
int r = index == k-1?n-1:divk[index+1];
// cout<<index<<":"<<l<<","<<r<<endl;
int i;
for(i=l;i<=r;i++){
cnt[v[i]]++;
// cout<<v[i]<<" "<<cnt[v[i]]<<endl;
}

int ansi = l;
long long dermin = 100000000000;
long long dernow = 0;
long long derori = 100000000000;
for(i=l;i<r;i++){
cntnow[v[i]]++;
long long oril = cntnow[v[i]]-1;
long long orir = cnt[v[i]] - oril;
long long oriv = cost[oril]+cost[orir];

long long nowl = oril+1;
long long nowr = orir-1;
long long nowv = cost[nowl]+cost[nowr];

dernow += -oriv+nowv;
if(i == divk[index]){
derori = dernow;
}
if(dernow < dermin || (dernow==dermin && i==divk[index])){
dermin = dernow;
ansi = i;
}
}
if(dermin < derori){
if(ansi != divk[index]){
// cout<<index<<": ["<<l<<","<<r<<"]"<<"der:"<<dermin-derori<<" | "<<divk[index]<<" -> "<<ansi<<endl;
divk[index] = ansi;
ans += dermin-derori;
return true;
}
}
return false;
}

int main(){
init();
cin>>n>>k;
k--;
int i;
for(i=0;i<n;i++)
scanf("%lld",&v[i]);

for(i=0;i<k;i++)
divk[i]=i;

for(i=k;i<n;i++){
cnt[v[i]]++;
}

for(i=1;i<=n;i++){
ans += cost[cnt[i]];
}

bool adjust=true;

while(adjust){
adjust=false;
for(i=0;i<k;i++){
if(doaj(i))
adjust=true;
}
}

cout<<ans<<endl;
return 0;
}

下一个想法是动态规划,dp[p][k]返回把[0 , p-1]分为k组的最小代价,则答案为dp[n][k]

其中再进行划分k组[0 , p-1] -> k-1组[0 , j-1] + 1组[j,p-1]

dp[p][k] = min{dp[j][k-1] + cost[j,p-1]}

有了这个 时间复杂度在接近 $O(n^3)$ , 对一个固定的k遍历p需要n,对一个p遍历j需要n,对一个j计算cost[j,p-1]需要n

分析:

k遍历p 最外层难优化

考虑最里层cost[j,p-1] 因为相邻的j的访问 会使这个函数左右范围变化不大,所以如果能用全局或者静态变量来维护左、右指向以及和,就能对这里的时间复杂度降低到O(1)

1
2
3
4
5
6
7
8
9
10
static long long cost(int l,int r){
static int _l = 0;
static int _r = -1;
static long long _ans = 0;
while(_r < r) _ans += cnt[v[++_r]]++;
while(_l > l) _ans += cnt[v[--_l]]++;
while(_r > r) _ans -= --cnt[v[_r--]];
while(_l < l) _ans -= --cnt[v[_l++]];
return _ans;
}

这样总的时间复杂度还是在O(n^2)

如果可以对一个p 我们的j的范围不再是[1~p-1],而能更小,那么时间复杂度也就还能下降

下面假设对给定p最小的分割为m,也就是dp[p][k]的最小值为dp[m][k-1] + cost[m,p-1]

_____________m-1|m_________p-1|p___________

______n-1|n_____|__________p-1|p_____|_____

______n-1|n_____|_____________|___q-1|q____

对于一个q>p 最小的分割为n,也就是dp[q][k]最小值为dp[n][k-1] + cost[n,q-1]

1
2
3
4
5
dp[n][k-1] + cost[n,q-1]
= dp[n][k-1] + cost[n,p-1] + der[n->q)[p->q)
>= dp[p][k] + der[n->q)[p->q)
= dp[m][k-1]+cost[m,p-1] + der[n->p)[p->q)
>= dp[m][k-1]+cost[m,p-1] + der[m->p)[p->q)

以上,其中der[base][add]表示在base的基础上再增加add部分的 新增加的cost

即是 在m左侧的最终划分都>=m处的划分,因此 如果在p计算出了m,之后再计算q的分割点时遍历只需要从m->q

同理可证当q<p时 更小的值只会在m左侧

因此 有了这个关系 就可以进行二分搜索,时间复杂度也就降到了O(n log n) [好像也不完全是个这个 估计比这个大一点 因为这样二分以后cost函数的均摊就不是O(1)的样子了]

实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#define MAX 10000000000

using namespace std;
long long minn(long long v1,long long v2){return v1<v2?v1:v2;}
long long maxx(long long v1,long long v2){return v1>v2?v1:v2;}

long long v[100010]={0};
long long cnt[100010]={0};
long long dp[100010][30]={0};

int n,k;

static long long cost(int l,int r){
static int _l = 0;
static int _r = -1;
static long long _ans = 0;
while(_r < r) _ans += cnt[v[++_r]]++;
while(_l > l) _ans += cnt[v[--_l]]++;
while(_r > r) _ans -= --cnt[v[_r--]];
while(_l < l) _ans -= --cnt[v[_l++]];
// cout<<"["<<_l<<","<<_r<<"]"<<_ans<<endl;
return _ans;
}

void solve(const int & kk,int l,int r,int mathl,int mathr){
// cout<<"---------------:"<<l<<" "<<r<<" "<<mathl<<" "<<mathr<<" "<<endl;
if(l > r)
return ;
int j = (l+r)/2;
int min_pos = mathl;
dp[j][kk] = MAX;
int maxi = minn(j,mathr);
int starti = maxx(kk-1,mathl);
int i;
for(i=starti;i<=maxi;i++){
long long newv = dp[i][kk-1] + cost(i,j-1);
// cout<<l<<" "<<r<<" "<<j<<" "<<i<<" "<<newv<<endl;
if(newv < dp[j][kk]){
dp[j][kk] = newv;
min_pos = i;
}
}
solve(kk, l , j-1, mathl , min_pos);
solve(kk, j+1, r , min_pos, mathr);
}

int main(){
cin>>n>>k;
int i,j,o;
for(i=0;i<n;i++)
scanf("%lld",&v[i]);

dp[0][1] = 0;
for(i=1;i<=n;i++)dp[i][1] = cost(0,i-1);

for(i=2;i<=k;i++)
solve(i,1,n,1,n);
cout<<dp[n][k]<<endl;

return 0;
}

AC

DP Optimizations

F - Best Representation

给小写字母字符串S

问S能最少被切割成多少个(?),使得每个子串都不能表示成循环节(ababa好,ababab坏)

有多少个方案 能 达到最小的切割数 mod 1e9+7

|S| 5e5

2s

256mb

我的思路

如果整个字符串是好的,那么答案为1,1

否则 整个字符串 如果是坏的,那么它可以表示成 字符串t的重复拼接

那么 最小循环节 长度如果 > 1,那么删去最后一个 是否是一个方案呢??

1
2
3
[.....][.....][.....][.....][.....][.....]
[...][...][...][...][...][...][...][...]
x

注意到这个两个数差为1,所以gcd(len)=1

而它们相互间 多个不同偏移量的对应相等,将会像gcd过程一样对位相等

所以不可能

所以 最小分割一定是2


所以问题变成了,整个字符串有多少个切割让 left right 都是好的

同样的, 只要不是最小切割的倍数即可


所以感觉就过了?

还是有一点点问题


1
2
3
111222111222
xx
yyyyyyyyyy

对于S的最小循环节 内还是有可能有更小的 循环的存在的

判断也好判断,如果要长度len的 循环节长度是cycle,那么len-cycle的循环节的长度是cycle的因数,注意前缀后缀都要算


然而 https://atcoder.jp/contests/arc060/submissions/50471607

ACx63,WAx2

subtask1_03.txt 和 subtask2_01.txt 炸了


1
2
3
4
5
6
7
abaababaab  
len(s) = 10, cycle size = 5
brute force = 8
left cycle, right not cycle, [0,5][6,9]
abaaba
2
9

这样的话虽然 baab是好的,但是左边是坏的且超过了单个循环节的长度

閱讀全文 »

F - Unhappy Hacking

https://atcoder.jp/contests/arc059/tasks/arc059_d

操作 push(0),push(1),pop(),其中pop在空时也可以操作

给定0/1串,问有多少不同n次的操作序列 能得到s, 方案数mod1e9+7

n 5000

2s

256mb

我的思路

对于 开头 需要计算 多少次可以在空时pop的操作 得到长度0

对于 中间 需要计算 多少次不可以在空时pop的操作 得到长度0

那么 用生成函数表示

$[x^t]g_0(x)$ 表示 $t$次操作长度0的第一种操作

$[x^t]f_0(x)$ 表示 $t$次操作长度0的第二种操作

那么有$ans = [x^{n}] g_0(x) (\cdot x \cdot f_0(x))^{|s|}$

$= [x^{n}] g_0(x) \cdot x^{|s|} \cdot f_0(x)^{|s|}$

$= [x^{n-|s|}] g_0(x) \cdot f_0(x)^{|s|}$

后面直接多项式快速幂就好了,1e9+7 原根太小,没法ntt

閱讀全文 »
0%