C大意

给一个有权无向图。进行q次询问,第i次询问ki个边,问所提到的ki个边能否同时出现在图的最小生成树上,返回YES或NO。

数据范围

图的边和点<=500'000

询问数q<=500'000,所有询问的边数的和sum(ki)<=500'000

时间空间(2s/256MB)

题解

不知道如果当时继续想下去,会不会想出解法,当时已经想到 并查集 和 边+未选取的边形成的环,已选取的 不能 大于max(未选取的)。感觉和正解的具体实现还差很远,但基础结论已经有了

标答说成那样,我也是没啥办法,我也想到了,没想到下一步。然后观摩了moejy0viiiiiv大佬的代码。 下面用举例来说明

首先,不要用官方的样例来想,想象图有且只有一个环,环上的边长是1,1,2,2,2

很直接可以知道只要不是选了所有的2,那么 都是可以同时存在于一个MST上的,那么选三个2的情况发生了什么,也就是上面说的选了三个2以后,在这个环上,未选取的最大只是1了。然后换一个角度看,如果我们已经先把图上所有的长度为1的链接好了,那么在新增加2的时候,就不应该形成环。

同样可以想象环上是1,1,2,2,2,3的情况,现在 尝试添加2的时候,就算全添加了,也不会直接形成环,和结论全选了2也能同时存在于一个MST是一样的。

其中要注意的是对于第一种情况,就算 询问中没有1 1,也不能选取 所有的2

因此把上述方法转化为伪代码

1
2
3
4
5
6
7
8
首先读入,所有边和询问,将询问 每个拆分,按长度第一序,询问号第二序 排序

从空开始,分别处理每一个询问中的1,将它们连接(并查集) 检验是否成环,如果成环该询问不可行

将所有的1相连接(并查集)不论在不在查询中,分别处理每一个询问中的2,将它们连接(并查集) 检验是否成环,如果成环该询问不可行
将所有的1 2相连接(并查集)不论在不在查询中,分别处理每一个询问中的3,将它们连接(并查集) 检验是否成环,如果成环该询问不可行
...
将所有的1 2...499'999相连接(并查集)不论在不在查询中,分别处理每一个询问中的500'000,将它们连接(并查集) 检验是否成环,如果成环该询问不可行

整理上面成循环就是

1
2
3
4
5
6
7
i=0
S(i) 表示连接完小于等于i的边的并查集
for(length=1~500'000):
遍历每一查询
选中一个包含该length的一个查询Q
将Q中所有长length的边 和 S(length-1)相连 检查是否成环
将所有长为length的边连入S(length-1)也就变成了S(length)

上面代码中 一个是既要利用已经连接的小于length的并查集,又要在每次 进行成环检测时 并查集能够复用,具体实现见下面moejy0viiiiiv大佬的代码,他用f来实现 可以逐渐增加利用的并查集,用p和mt来实现可复用 的每次检测。

代码

moejy0viiiiiv 的代码

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
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <cassert>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
// head

const int N=501000;

int n,m,u[N],v[N],w[N],f[N],p[N],mt[N],T,wa[N];
int Q,k,id;
vector<int> eg[N];
vector<PII> qw[N];

int find(int x) {
if (f[x]!=x) return f[x]=find(f[x]); else return x;
}
int find2(int x) {
if (mt[x]!=T) mt[x]=T,p[x]=f[x];
if (p[x]!=x) return p[x]=find2(p[x]); else return x;
}

int main() {
scanf("%d%d",&n,&m);
rep(i,0,m) {
scanf("%d%d%d",u+i,v+i,w+i);
eg[w[i]].pb(i);
}
rep(i,1,n+1) f[i]=i;
scanf("%d",&Q);
rep(i,0,Q) {
scanf("%d",&k);
rep(j,0,k) {
scanf("%d",&id);
--id;
qw[w[id]].pb(mp(i,id));
}
}
rep(i,1,500001) {
sort(all(qw[i]));
rep(j,0,SZ(qw[i])) {
int x=u[qw[i][j].se],y=v[qw[i][j].se];
find(x); find(y);
}
rep(j,0,SZ(qw[i])) {
if (j==0||qw[i][j].fi!=qw[i][j-1].fi) T++;
int x=u[qw[i][j].se],y=v[qw[i][j].se];
if (find2(x)==find2(y)) wa[qw[i][j].fi]=1;
p[find2(x)]=find2(y);
}
for (auto id:eg[i]) {
int x=u[id],y=v[id];
f[find(x)]=find(y);
}
}
rep(i,0,Q) puts(wa[i]?"NO":"YES");
}

参考

题目

官方题解

E

给平面上n个点,在每个点可以,画一条竖直线,画一条横直线,什么都不画,也就是不能画十字

这样n个点一共能画出多少种图形

数据范围

n<=100 000,点的坐标范围-1 000 000 000<=x,y<=1 000 000 000

时间空间(2s/256MB)

输出答案MOD 1 000 000 007

题解

想了半天还想了递推,但是到横竖交叉我的递推就变得格外的复杂以至不可用。

我已经想到 如果有一群点可以通过横线和竖线连在一起,那么,我们只用考虑画出来的横线竖线的可能性

题解的意思是需要发现两个事实:

  1. 如果 这一群点能够成环,那么方案数=2^(横线数+竖线数)

  2. 如果 这一群点不能够成环,那么方案数=2^(横线数+竖线数) - 1

过程就是并查集之类的+找环就好了

所以我菜 就菜在 根本没有发现这两个 事实

代码/280ms/14368KB

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
typedef long long ll;

#define shi5 100000+10
#define MOD 1000000007

#include <bits/stdc++.h>
using namespace std;
struct poi{
ll x;
ll y;
int color;
};
// int structcmp(const void *v1,const void *v2){return ((mystruct *)v1)->v - ((mystruct *)v2)->v;}
int intcmp(const void *v1,const void *v2){return *(int *)v1-*(int *)v2;}
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;}

poi p[100010]={0};
map<ll,vector<int> > x2index;
map<ll,vector<int> > y2index;

ll twopow(ll val){
if(val == 1)
return 2;
ll ret = twopow(val/2);
ret *= ret;
ret %= MOD;
if(val%2)
ret *= 2;
ret %= MOD;
return ret;
}

ll getnocircle(int v){
ll ret = twopow(v);
if(ret == 0)
return MOD-1;
else
return ret-1;
}

ll getcircle(int v){
return twopow(v);
}

ll dolink(int index,int c){
map<ll,ll> xlstartindex;
map<ll,ll> ylstartindex;

vector<ll> xlist;
vector<ll> ylist;
int xitr=0,yitr=0;
bool iscircle=false;

p[index].color=c;
xlist.push_back(p[index].x);
ylist.push_back(p[index].y);
xlstartindex[p[index].x] = index;
ylstartindex[p[index].y] = index;

while(xitr != xlist.size() or yitr != ylist.size()){
if(xitr != xlist.size()){
for(auto indexeach:x2index[xlist[xitr]]){
if(indexeach == xlstartindex[xlist[xitr]])
continue;
if(p[indexeach].color != 0)
iscircle = true;
else if(ylstartindex.count(p[indexeach].y)){
p[indexeach].color = c;
iscircle = true;
}else{
p[indexeach].color = c;
ylist.push_back(p[indexeach].y);
ylstartindex[p[indexeach].y] = indexeach;
}
}
xitr++;
}
if(yitr != ylist.size()){
for(auto indexeach:y2index[ylist[yitr]]){
if(indexeach == ylstartindex[ylist[yitr]])
continue;
if(p[indexeach].color != 0)
iscircle = true;
else if(xlstartindex.count(p[indexeach].x)){
p[indexeach].color = c;
iscircle = true;
}else{
p[indexeach].color = c;
xlist.push_back(p[indexeach].x);
xlstartindex[p[indexeach].x] = indexeach;
}
}
yitr++;
}
}
if(iscircle)
return getcircle(xlist.size()+ylist.size());
else
return getnocircle(xlist.size()+ylist.size());
}


int main(){
int n;
cin>>n;
int i;
for(i=0;i<n;i++){
scanf("%lld %lld",&p[i].x,&p[i].y);
x2index[p[i].x].push_back(i);
y2index[p[i].y].push_back(i);
}

int nowcolor=1;
ll ans = 1;
for(i=0;i<n;i++){
if(p[i].color == 0){
ans *= dolink(i,nowcolor);
ans %= MOD;
}
nowcolor++;
}
cout<<ans<<endl;

return 0;
}

依旧代码丑陋126行

大佬们都写得十分精简 低头

oscillation

wdmmsyf

参考

题目

官方题解

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

0%