JSOI 套题乱做

JSOI 解题报告

[JSOI2010] 旅行

链接

Luogu P6029

BZOJ 4681

题解

动态规划。

先把边按照边权从小到大排序,然后钦定前 $x$ 条边在路径上。

于是我们可以考虑设 $dp_{i,j,k}$ 表示到了点 $i$ 经过 $j$ 条被钦定的边使用魔法次数为 $k$ 的最短路,于是就可以转移了:

假设我们现在考虑到的边是 $u\rightarrow v$,那么

如果钦定了这条边:$dp_{u,j,k}+w_{j+1}\rightarrow dp_{v,j+1,k}$

如果没有钦定这条边:

  • 使用魔法:$dp_{u,j,k}+w_{j+1}\rightarrow dp_{v,j+1,k+1}$

  • 不使用魔法:$dp_{u,j,k}+\operatorname{dist}(u,v)\rightarrow dp_{v,j+1,k+1}$

每次转移用类似 $\texttt{Dijkstra}$ 的方法跑一遍即可。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=161;
struct Edge{
ll to,prev,dist;
};
struct EdgeForSort{
ll from,to,dist;
inline bool operator <(const EdgeForSort &rhs)const
{
return this->dist<rhs.dist;
}
};
struct Tuple{
ll x,l,kk,dist;
inline bool operator <(const Tuple &rhs)const
{
return this->dist>rhs.dist;
}
};
Edge ed[MAXN<<1];
EdgeForSort edd[MAXN];
ll nc,ec,kk,tot,limit,res=0x3f3f3f3f;
ll last[MAXN],dis[MAXN][MAXN][MAXN],inQueue[MAXN][MAXN][MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void addEdge(ll from,ll to,ll dist)
{
ed[++tot].prev=last[from];
ed[tot].to=to;
ed[tot].dist=dist;
last[from]=tot;
}
inline void Dijkstra()
{
priority_queue<Tuple>q;
ll x,l,kk,dist;
memset(dis,0x3f,sizeof(dis));
dis[1][0][0]=0,inQueue[1][0][0]=1,q.push((Tuple){1,0,0,0});
while(!q.empty())
{
x=q.top().x,l=q.top().l,kk=q.top().kk,dist=dis[x][l][kk],q.pop(),inQueue[x][l][kk]=0;
for(register int i=last[x];i;i=ed[i].prev)
{
if(i<=limit)
{
if((l<<1)<limit&&dist+ed[(l+1)<<1].dist<dis[ed[i].to][l+1][kk])
{
dis[ed[i].to][l+1][kk]=dist+ed[(l+1)<<1].dist;
if(!inQueue[ed[i].to][l+1][kk])
{
inQueue[ed[i].to][l+1][kk]=1;
q.push((Tuple){ed[i].to,l+1,kk,dis[ed[i].to][l+1][kk]});
}
}
}
else
{
if((l<<1)<limit&&kk<::kk&&dist+ed[(l+1)<<1].dist<dis[ed[i].to][l+1][kk+1])
{
dis[ed[i].to][l+1][kk+1]=dist+ed[(l+1)<<1].dist;
if(!inQueue[ed[i].to][l+1][kk+1])
{
inQueue[ed[i].to][l+1][kk+1]=1;
q.push((Tuple){ed[i].to,l+1,kk+1,dis[ed[i].to][l+1][kk+1]});
}
}
if(dist+ed[i].dist<dis[ed[i].to][l][kk])
{
dis[ed[i].to][l][kk]=dist+ed[i].dist;
if(!inQueue[ed[i].to][l][kk])
{
inQueue[ed[i].to][l][kk]=1;
q.push((Tuple){ed[i].to,l,kk,dis[ed[i].to][l][kk]});
}
}
}
}
}
for(register int i=0;i<=::kk;i++)
{
res=min(res,dis[nc][limit>>1][i]);
}
}
int main()
{
nc=read(),ec=read(),kk=read();
for(register int i=1;i<=ec;i++)
{
edd[i].from=read(),edd[i].to=read(),edd[i].dist=read();
}
sort(edd+1,edd+ec+1);
for(register int i=1;i<=ec;i++)
{
addEdge(edd[i].from,edd[i].to,edd[i].dist);
addEdge(edd[i].to,edd[i].from,edd[i].dist);
}
for(register int i=1;i<=ec;i++)
{
limit=i<<1,Dijkstra();
}
printf("%d\n",res);
}

[JSOI2010] 找零钱的洁癖

链接

BZOJ 5021

题解

网上有人说这道题是错题。

考虑搜索。当前考虑的硬币有正负两种贡献方式。

然而搜索可能会超时,需要设定一个不同值进队次数的阈值来保证不会搜索过多次,但是这样可能无法搜出结果。

考虑贪心,将硬币按照面值排序,从大往小选即可。(为什么呢,因为那些搜索没有跑出答案的数据硬笔面值全都是 $2$ 的幂,所以直接二进制贪心)

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const ll MAXN=1e5+51;
struct Tuple{
ll num,res;
};
queue<Tuple>q;
unordered_map<ll,bool>mp;
ll x,cnt,res,sz;
ll coin[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void bfs()
{
ll num,rres;
q.push((Tuple){0,0});
while(!q.empty())
{
num=q.front().num,rres=q.front().res,q.pop();
if(sz>=2500000)
{
res=0x3f3f3f3f;
return;
}
for(register int i=0;i<cnt;i++)
{
if(mp.find(num+coin[i])==mp.end())
{
mp[num+coin[i]]=1,q.push((Tuple){num+coin[i],rres+1}),sz++;
if(num+coin[i]==x)
{
res=rres+1;
return;
}
}
if(mp.find(num-coin[i])==mp.end())
{
mp[num-coin[i]]=1,q.push((Tuple){num-coin[i],rres+1}),sz++;
if(num-coin[i]==x)
{
res=rres+1;
return;
}
}
}
}
}
inline void greedy()
{
ll tmp=x,rres=0;
for(register int i=cnt-1;i>=0;i--)
{
rres+=tmp/coin[i],tmp-=tmp/coin[i]*coin[i];
}
res=min(res,tmp?0x3f3f3f3f:rres);
}
int main()
{
x=read();
for(;scanf("%lld",&coin[cnt++])==1;);
sort(coin,coin+(--cnt)),bfs(),greedy(),printf("%lld\n",res);
}

[JSOI2010] 俄罗斯方块

链接

BZOJ 5022

题解

毒瘤题。

注意到,$7$ 种方块通过旋转最多构造出 $19$ 中摆放方法。预处理一下相对高度和绝对高度即可。

然后方块数量很少,可以状压并且使用线段树维护。每一次直接暴力判断并且暴力修改该方块左右的一些高度即可。

代码

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=2e5+51;
struct SegmentTree{
ll l,r,ht,state;
};
SegmentTree tree[MAXN<<2];
ll cnt,ccnt,height,p,x,t,tt;
ll ht[MAXN],state[MAXN];
ll id[19]={0,0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,5,6,6};
ll rot[7][4]={
{0,1,2,3},{4,5,6,7},{8,9,0,0},{10,11,0,0},
{12,13,14,15},{16,0,0,0},{17,18,0,0}
};
ll addx[19][4]={
{3,1,0,0},{1,1,2,0},{1,3,0,0},{2,1,1,0},
{1,3,0,0},{1,1,2,0},{3,1,0,0},{2,1,1,0},
{1,2,1,0},{2,2,0,0},{1,2,1,0},{2,2,0,0},
{1,2,1,0},{1,3,0,0},{1,2,1,0},{3,1,0,0},
{2,2,0,0},{1,1,1,1},{4,0,0,0}
};
ll chkx[19][4]={
{2,0,-1,-1},{0,0,1,-1},{0,0,-1,-1},{0,0,0,-1},
{0,2,-1,-1},{0,0,0,-1},{0,0,-1,-1},{1,0,0,-1},
{1,1,0,-1},{0,1,-1,-1},{0,1,1,-1},{1,0,-1,-1},
{0,1,0,-1},{0,1,-1,-1},{0,0,0,-1},{1,0,-1,-1},
{0,0,-1,-1},{0,0,0,0},{0,-1,-1,-1}
};
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline bool check(ll x,ll y)
{
ll h=0;
if(ht[x]+addx[y][0]>height+p)
{
return 0;
}
h=ht[x]+chkx[y][0];
for(register int i=1;i<4;i++)
{
if(~chkx[y][i]&&(ht[x+i]+chkx[y][i]!=h||ht[x+i]+addx[y][i]>height+p))
{
return 0;
}
}
return 1;
}
inline void update(ll node)
{
tree[node].ht=min(tree[node<<1].ht,tree[(node<<1)|1].ht);
tree[node].state=tree[node<<1].state|tree[(node<<1)|1].state;
}
inline void create(ll l,ll r,ll node)
{
tree[node].l=l,tree[node].r=r;
if(l==r)
{
tree[node].ht=ht[l],tree[node].state=state[l];
return;
}
ll mid=(l+r)>>1;
create(l,mid,node<<1);
create(mid+1,r,(node<<1)|1);
update(node);
}
inline void changePoint(ll pos,ll node)
{
if(tree[node].l==tree[node].r)
{
tree[node].ht=ht[pos],tree[node].state=state[pos];
return;
}
ll mid=(tree[node].l+tree[node].r)>>1;
if(pos<=mid)
{
changePoint(pos,node<<1);
}
else
{
changePoint(pos,(node<<1)|1);
}
update(node);
}
inline ll queryState(ll x,ll node)
{
if(tree[node].l==tree[node].r)
{
return (tree[node].state&(1<<x))?tree[node].l:0;
}
if(!(tree[node].state&(1<<x)))
{
return 0;
}
ll res=queryState(x,node<<1);
return res?res:queryState(x,(node<<1)|1);
}
inline void clear(ll x)
{
state[x]=0;
for(register int i=0;i<19;i++)
{
if(check(x,i))
{
state[x]|=(1<<id[i]);
}
}
}
int main()
{
cnt=read(),ccnt=read(),height=read(),ht[cnt+1]=-0x3f3f3f3f;
for(register int i=1;i<=cnt;i++)
{
ht[i]=read();
}
for(register int i=1;i<=cnt;i++)
{
clear(i);
}
create(1,cnt,1);
for(register int i=1;i<=ccnt;i++)
{
x=read(),p=tree[1].ht,t=queryState(x,1),tt=0,printf("%d\n",t-1);
for(register int j=0;j<4;j++)
{
if(check(t,rot[x][j]))
{
tt=rot[x][j];
break;
}
}
for(register int j=0;j<4;j++)
{
if(t+j>cnt)
{
break;
}
ht[t+j]+=addx[tt][j],changePoint(t+j,1);
}
if(tree[1].ht==p)
{
for(register int j=t-3;j<=t+3;j++)
{
if(j>0&&j<=cnt)
{
clear(j),changePoint(j,1);
}
}
}
else
{
p=tree[1].ht;
for(register int j=1;j<=cnt;j++)
{
clear(j),ht[j]-=p;
}
create(1,cnt,1);
}
}
}

[JSOI2010] 挖宝藏

链接

BZOJ 5023

题解

注意到挖到一个宝藏后挖的坑是个倒着的三角形,于是我们就可以把宝藏看作是一段区间,然后通过区间长度计算代价。

考虑 $\texttt{dp}$。将宝藏按右端点排序,设 $dp_i$ 表示挖 $1\sim i$ 号宝藏,且钦定必须挖 $i$ 的答案。

枚举一下之前的宝藏 $j$,分成 $3$ 种情况

  • $j$ 被包含在 $i$ 内:挖 $i$ 的同时可以把 $j$ 挖出来,提前预处理即可。

  • $j$ 与 $i$ 无交:$dp_j+val_i-cost_i\rightarrow dp_i$

  • $j$ 与 $i$ 有交:要容斥掉交的区间的代价,但是有可能有些宝藏被包含在这个区间内。因为右端点递增,拿个指针扫一下,没了。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=1e3+51;
struct Node{
ll l,r,cost,p,q;
inline bool operator <(const Node &rhs)const
{
return this->r==rhs.r?this->l<rhs.l:this->r<rhs.r;
}
};
Node nd[MAXN];
ll cnt,x,y,tp,r,res;
ll dp[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline ll calc(ll x)
{
return (x+2-(x&1))*(x+(x&1))>>2;
}
int main()
{
cnt=read();
for(register int i=1;i<=cnt;i++)
{
x=read(),y=read(),nd[i].l=x+y+1,nd[i].r=x-y-1;
nd[i].p=read(),nd[i].cost=calc(nd[i].r-nd[i].l+1);
}
for(register int i=1;i<=cnt;i++)
{
for(register int j=1;j<=cnt;j++)
{
if(nd[j].l>=nd[i].l&&nd[j].r<=nd[i].r)
{
nd[i].q+=nd[j].p;
}
}
}
sort(nd+1,nd+cnt+1);
for(register int i=1;i<=cnt;i++)
{
tp=1,r=0,dp[i]=nd[i].q-nd[i].cost;
for(register int j=1;j<i;j++)
{
if(nd[j].r<nd[i].l)
{
dp[i]=max(dp[i],dp[j]+nd[i].q-nd[i].cost);
}
else
{
if(nd[j].l<nd[i].l)
{
while(tp<=i&&nd[tp].r<=nd[j].r)
{
if(nd[tp].l>=nd[i].l)
{
r+=nd[tp].p;
}
tp++;
}
dp[i]=max(dp[i],dp[j]+nd[i].q-nd[i].cost+calc(nd[j].r-nd[i].l+1)-r);
}
}
}
}
for(register int i=1;i<=cnt;i++)
{
res=max(res,dp[i]);
}
printf("%d\n",res);
}

[JSOI2010] 游戏

链接

BZOJ 5024

题解

什么神仙题啊。

考虑对链进行染色,如果链 $(x,y)$ 是良的就染成白色,否则就是黑色。

那么接下来就是完全图求同色三角形个数。

找一下角的个数 $x$,那么答案就是 $\binom{n}{3}-x$。

考虑快速判断某条链是否是良的,想到在模 $8$ 的意义下计算,于是就有

由于 $1=(001)_2,5=(101)_2$,所以考虑倒数第三位是否为 $0$。

而这一位可以由两个地方贡献过来:所有数和的倒数第三位和第偶数个数和的最后一位。

然而第一个已知,只要考虑第二个。

对数列讨论一下:

  • 情况 $0$,所有数的二进制最后一位都为 $0$。

    这时没有第二类贡献,直接算第一类即可。

  • 情况 $1$,所有数的二进制最后一位都为 $1$。

    这时第二类贡献为所有数最后一位贡献之和除以 $2$。

  • 情况 $2$,剩下的所有情况。

    使用类似于括号序列的方法即可。

然后树形 $\texttt{dp}$。设 $dp_{x,i,j,k}$ 表示当前在 $x$,路径上的和 $\bmod 8$ 为 $i$,最低位的和 $\bmod 4$ 为 $j$,情况为 $k$ 的子树中相应的点的个数。

换根 $\texttt{dp}$ 一下就好了。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=1e5+51;
struct Edge{
ll to,prev;
};
Edge ed[MAXN<<1];
ll nc,rt,tot,p;
li res;
ll last[MAXN],num[MAXN],dp[MAXN][8][4][3];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void addEdge(ll from,ll to)
{
ed[++tot].prev=last[from];
ed[tot].to=to;
last[from]=tot;
}
inline void add(ll x,ll op)
{
dp[x][num[x]&7][num[x]&1][num[x]&1]+=op;
}
inline void calc(ll x,ll y,ll op)
{
ll p,q,r;
for(register int i=0;i<8;i++)
{
for(register int j=0;j<4;j++)
{
p=(i+num[y])&7,q=num[y]&1,r=(j+q)&3;
for(register int k=0;k<3;k++)
{
dp[y][p][r][q^k?2:q]+=dp[x][i][j][k]*op;
}
}
}
}
inline void calc2(ll x)
{
li r=0;
for(register int i=0;i<4;i++)
{
r+=dp[x][3][i][0]+dp[x][3][i][2]+dp[x][7][i][2];
r+=dp[x][i&2?7:3][i][1];
}
res+=(li)r*(nc-1-r);
}
inline void dfs(ll node)
{
for(register int i=last[node];i;i=ed[i].prev)
{
dfs(ed[i].to),calc(ed[i].to,node,1);
}
add(node,1);
}
inline void ddfs(ll node)
{
add(node,-1),calc2(node),add(node,1);
for(register int i=last[node];i;i=ed[i].prev)
{
calc(ed[i].to,node,-1),calc(node,ed[i].to,1),ddfs(ed[i].to);
calc(node,ed[i].to,-1),calc(ed[i].to,node,1);
}
}
int main()
{
nc=read();
for(register int i=1;i<=nc;i++)
{
p=read(),num[i]=read()&7,p?addEdge(p,i):(void)(rt=i);
}
dfs(rt),ddfs(rt),printf("%lld\n",(li)nc*(nc-1)*(nc-2)/6-(res>>1));
}

[JSOI2010] 排名

链接

BZOJ 5026

题解

套路贪心题。

第一问建反图然后拓扑排序即可。

第二问直接从 $0$ 号同学(假想的虚拟人物,成绩最高)开始贪心即可。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=2e5+51;
struct Edge{
ll to,prev;
};
Edge ed[MAXN];
priority_queue<ll>q;
ll cnt,x,tot,cur;
ll last[MAXN],deg[MAXN],res[MAXN],p[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void addEdge(ll from,ll to)
{
ed[++tot].prev=last[from];
ed[tot].to=to;
last[from]=tot;
}
int main()
{
cnt=read(),cur=cnt;
for(register int i=1;i<=cnt;i++)
{
p[i]=x=read(),x=(x?x:cnt+1),addEdge(x,i),deg[x]++;
}
for(register int i=1;i<=cnt;i++)
{
if(!deg[i])
{
q.push(i);
}
}
while(!q.empty())
{
res[x=q.top()]=cur--,q.pop();
if(!(--deg[p[x]]))
{
q.push(p[x]);
}
}
for(register int i=1;i<=cnt;i++)
{
printf("%d ",res[i]);
}
puts(""),q.push(cnt+1);
while(!q.empty())
{
res[x=q.top()]=cur++,q.pop();
for(register int i=last[x];i;i=ed[i].prev)
{
q.push(ed[i].to);
}
}
for(register int i=1;i<=cnt;i++)
{
printf("%d ",res[i]);
}
}

[JSOI2011] Apple 的美食

链接

Luogu P6032

BZOJ 4708

题解

玄学题。

考虑先推柿子。

然后有一个结论:两个断点肯定只会在前 $100$ 和后 $100$ 个,暴力枚举即可。注意到 $n\leq 10^6$,不能暴力求,但是模数比 $20$ 小,所以可以求循环节。由于循环节可能达到 $n-1$,所以要 $\texttt{kmp}$ 求。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=2e5+51;
struct Edge{
ll to,prev;
};
Edge ed[MAXN];
priority_queue<ll>q;
ll cnt,x,tot,cur;
ll last[MAXN],deg[MAXN],res[MAXN],p[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void addEdge(ll from,ll to)
{
ed[++tot].prev=last[from];
ed[tot].to=to;
last[from]=tot;
}
int main()
{
cnt=read(),cur=cnt;
for(register int i=1;i<=cnt;i++)
{
p[i]=x=read(),x=(x?x:cnt+1),addEdge(x,i),deg[x]++;
}
for(register int i=1;i<=cnt;i++)
{
if(!deg[i])
{
q.push(i);
}
}
while(!q.empty())
{
res[x=q.top()]=cur--,q.pop();
if(!(--deg[p[x]]))
{
q.push(p[x]);
}
}
for(register int i=1;i<=cnt;i++)
{
printf("%d ",res[i]);
}
puts(""),q.push(cnt+1);
while(!q.empty())
{
res[x=q.top()]=cur++,q.pop();
for(register int i=last[x];i;i=ed[i].prev)
{
q.push(ed[i].to);
}
}
for(register int i=1;i<=cnt;i++)
{
printf("%d ",res[i]);
}
}

[JSOI2011] 柠檬

链接

Luogu P5504

BZOJ 4709

题解

斜率优化 $\texttt{dp}$ 入门题。

首先有个显然的性质:每一段的两端的数相同。

如果不同的话,那些不同的数也不会给这一段有贡献,反而把它们分到另外的段去期望对其他段产生贡献,才能使贡献最大化。

于是可以设 $dp_i$ 表示前面 $i$ 个数产生的最大贡献,$c_i$ 表示这个数是第几次出现,则有

可这是 $O(n^2)$ 的,跑不过,考虑斜率优化。

观察一下原来的式子,把与 $j$ 有关的放到 $y,x$ 项,与 $j$ 无关的放到 $k,b$ 项,则有

移项之后,有

于是,有

于是考虑用一个单调栈维护 $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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=1e5+51;
vector<ll>q[MAXN>>3];
ll cnt,cur,top,tmp;
li num[MAXN],occ[MAXN],st[MAXN],x[MAXN],y[MAXN],dp[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline double slope(ll xx,ll yy)
{
return (double)(y[yy]-y[xx])/(double)(x[yy]-x[xx]);
}
int main()
{
cnt=read();
for(register int i=1;i<=cnt;i++)
{
num[i]=read(),st[i]=++occ[num[i]];
}
for(register int i=1;i<=cnt;i++)
{
cur=num[i],top=q[cur].size()-1;
x[i]=(st[i]-1)*cur,y[i]=x[i]*(st[i]-1)+dp[i-1];
while(top>0&&slope(q[cur][top-1],q[cur][top])<slope(q[cur][top],i))
{
q[cur].pop_back(),top--;
}
q[cur].push_back(i),top++;
while(top>0&&slope(q[cur][top-1],q[cur][top])<2*st[i])
{
q[cur].pop_back(),top--;
}
tmp=q[cur][top];
dp[i]=dp[tmp-1]+cur*(st[i]-st[tmp]+1)*(st[i]-st[tmp]+1);
}
printf("%lld",dp[cnt]);
}

[JSOI2011] 分特产

链接

Luogu P5505

BZOJ 4710

题解

先考虑没有每个人至少一个的限制,那么答案就是可重组合,也就是

然后既然有了这个限制不太好处理的话。那么容斥做肯定可以。

考虑至少有 $t$ 个同学没有的情况,那么钦定前 $t$ 个同学没有特产,后面的有不有都无所谓,因为这里求的是至少 $t$ 个同学没有,所以答案为 $f(n-t)$

于是可以容斥出答案,就是这个:

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=5e3+51,MOD=1e9+7;
ll cnt,ccnt,maxn,res,coeff;
ll fact[MAXN],finv[MAXN],num[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline ll qpow(ll base,ll exponent)
{
ll res=1;
while(exponent)
{
if(exponent&1)
{
res=(li)res*base%MOD;
}
base=(li)base*base%MOD,exponent>>=1;
}
return res;
}
inline void setup(ll cnt)
{
fact[0]=fact[1]=finv[0]=1;
for(register int i=2;i<=cnt;i++)
{
fact[i]=(li)fact[i-1]*i%MOD;
}
finv[cnt]=qpow(fact[cnt],MOD-2);
for(register int i=cnt-1;i;i--)
{
finv[i]=(li)finv[i+1]*(i+1)%MOD;
}
}
inline ll comb(ll m,ll n)
{
return (li)fact[m]*finv[n]%MOD*finv[m-n]%MOD;
}
int main()
{
cnt=read(),ccnt=read();
for(register int i=0;i<ccnt;i++)
{
num[i]=read(),maxn=max(maxn,num[i]);
}
setup(cnt+maxn);
for(register int i=0;i<=cnt;i++)
{
coeff=1;
for(register int j=0;j<ccnt;j++)
{
coeff=(li)coeff*comb(num[j]+cnt-i-1,cnt-i-1)%MOD;
}
coeff=(li)coeff*comb(cnt,i)%MOD;
res=(res+((i&1)?MOD-coeff:coeff))%MOD;
}
printf("%d",res);
}

[JSOI2011] 棒棒糖

链接

BZOJ 5178

题解

莫队。

考虑直接用莫队维护一下当前区间内颜色的集合。然后每一次询问考虑最多的颜色是否超过区间的一般就好了。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=1e5+51;
struct Tuple{
ll x,y;
inline bool operator <(const Tuple &rhs)const
{
return this->x==rhs.x?this->y<rhs.y:this->x<rhs.x;
}
};
struct Query{
ll l,r,ord;
inline bool operator <(const Query &rhs)const;
};
set<Tuple>st;
set<Tuple>::iterator it;
Query qry[MAXN];
ll cnt,ccnt,blockSize,l,r,ptrl,ptrr;
ll num[MAXN],res[MAXN],cntl[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline bool Query::operator <(const Query &rhs)const
{
return (r/blockSize)==(rhs.r/blockSize)?l<rhs.l:r<rhs.r;
}
inline void add(ll cur)
{
set<Tuple>::iterator it=st.find((Tuple){cntl[num[cur]],num[cur]});
if(it!=st.end())
{
st.erase(it);
}
st.insert((Tuple){++cntl[num[cur]],num[cur]});
}
inline void del(ll cur)
{
set<Tuple>::iterator it=st.find((Tuple){cntl[num[cur]],num[cur]});
if(it!=st.end())
{
st.erase(it);
}
st.insert((Tuple){--cntl[num[cur]],num[cur]});
}
int main()
{
blockSize=sqrt(cnt=read()),ccnt=read();
for(register int i=1;i<=cnt;i++)
{
num[i]=read();
}
for(register int i=0;i<ccnt;i++)
{
l=read(),r=read(),qry[i]=(Query){l,r,i};
}
sort(qry,qry+ccnt);
for(register int i=0;i<ccnt;i++)
{
while(ptrl<qry[i].l)
{
del(ptrl++);
}
while(ptrl>qry[i].l)
{
add(--ptrl);
}
while(ptrr<qry[i].r)
{
add(++ptrr);
}
while(ptrr>qry[i].r)
{
del(ptrr--);
}
res[qry[i].ord]=st.rbegin()->x>(qry[i].r-qry[i].l+1)/2?st.rbegin()->y:0;
}
for(register int i=0;i<ccnt;i++)
{
printf("%d\n",res[i]);
}
}

[JSOI2011] 任务调度

链接

题解

左偏树模板题。

权值最小任务不唯一也就是说根与某个儿子的权值相等,利用这个性质即可。

剩下的操作都是基本操作,直接左偏树维护。

代码

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
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=3e5+51;
ll cnt,x,y,z,flg;
char op[10];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
namespace LeftistTree{
struct Node{
ll val;
inline bool operator <(const Node &rhs)const
{
return this->val<rhs.val;
}
};
Node nd[MAXN];
ll rt[551],fa[MAXN],ls[MAXN],rs[MAXN],dist[MAXN],del[MAXN];
ll merge(ll x,ll y)
{
if(!x||!y)
{
return x+y;
}
if(nd[y]<nd[x])
{
swap(x,y);
}
rs[x]=merge(rs[x],y),fa[rs[x]]=x;
if(dist[ls[x]]<dist[rs[x]])
{
swap(ls[x],rs[x]);
}
dist[x]=dist[rs[x]]+1;
return x;
}
inline ll add(ll x,ll y,ll z)
{
ll f=fa[x],p=merge(ls[x],rs[x]);
if(rt[y]==x)
{
rt[y]=p;
}
if(ls[f]==x)
{
ls[f]=p;
}
else
{
if(rs[f]==x)
{
rs[f]=p;
}
}
fa[p]=f,ls[x]=rs[x]=fa[x]=dist[x]=0,nd[x].val+=z,rt[y]=merge(rt[y],x);
}
}
using namespace LeftistTree;
int main()
{
read(),read(),cnt=read(),dist[0]=-1;
for(register int i=0;i<cnt;i++)
{
scanf("%s",op);
if(op[0]=='A')
{
x=read(),y=read(),z=read(),nd[y].val=z,rt[x]=merge(rt[x],y);
}
if(op[0]=='D')
{
x=read(),y=read(),z=read();
add(y,x,-z);
}
if(op[0]=='T')
{
x=read(),y=read(),rt[y]=merge(rt[y],rt[x]),rt[x]=0;
}
if(op[0]=='M')
{
printf("%d\n",nd[rt[read()]].val);
}
if(op[0]=='W')
{
x=read(),y=read(),flg=1;
if(ls[rt[x]]&&nd[ls[rt[x]]].val==nd[rt[x]].val)
{
flg=0;
}
if(rs[rt[x]]&&nd[rs[rt[x]]].val==nd[rt[x]].val)
{
flg=0;
}
if(flg)
{
add(rt[x],x,y);
}
else
{
puts("ERROR");
}
}
}
}