A. Hilbert’s Hotel
略
B. Monopole Magnets
略
C. Quantifier Question
略
D. Résumé Review
设
f
(
i
,
x
)
f(i,x)
f(i,x)(
0
≤
x
<
a
i
0\leq x<a_i
0≤x<ai)表示
b
i
b_i
bi由
x
x
x变为
x
+
1
x+1
x+1答案的增量,那么有
f
i
(
x
)
=
(
x
+
1
)
(
a
i
−
(
x
+
1
)
2
)
−
x
(
a
i
−
x
2
)
=
−
3
x
2
−
4
x
+
(
a
−
1
)
f_i(x)=(x+1)(a_i-(x+1)^2)-x(a_i-x^2)=-3x^2-4x+(a-1)
fi(x)=(x+1)(ai−(x+1)2)−x(ai−x2)=−3x2−4x+(a−1),在定义域内单调递减。
那么有一个显然的贪心是一开始让所有的
b
i
=
0
b_i=0
bi=0,每次选一个
b
i
<
a
i
b_i<a_i
bi<ai且
f
(
i
,
b
i
)
f(i,b_i)
f(i,bi)最大的
b
i
b_i
bi增加
1
1
1,重复
k
k
k次。
分析一下,这个贪心算法其实是选择了前
k
k
k大的
f
(
i
,
x
)
f(i,x)
f(i,x)。那么显然可以考虑二分一下第
k
k
k大的
f
(
i
,
x
)
f(i,x)
f(i,x),假设我们二分了一个
m
i
d
mid
mid,只需要对每个
i
i
i计算有多少
f
(
i
,
x
)
≥
m
i
d
f(i,x)\geq mid
f(i,x)≥mid,这个可以再二分或者直接解一个二次不等式。
复杂度取决于内层计算
f
(
i
,
x
)
≥
m
i
d
f(i,x)\geq mid
f(i,x)≥mid的解数的方式,这份代码选择了直接二分,时间复杂度为
O
(
n
log
2
V
)
\mathcal O(n\log^2V)
O(nlog2V),也可以优化到
O
(
n
log
V
)
\mathcal O(n\log V)
O(nlogV)。
#include <bits/stdc++.h>
#define FR first
#define SE second
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef __int128 lll;
typedef pair<int,int> pr;
inline ll F(ll x,ll y) {
return x-1-3LL*y*y-4LL*y;
}
ll calc(ll x,ll d) {
if (x-1<d) return 0;
ll l=0,r=x-1;
while (l<r) {
ll mid=((l+r)>>1)+1;
if (F(x,mid)>=d) l=mid; else r=mid-1;
}
return l+1;
}
ll num[100005];
ll check(int n,ll d) {
ll s=0;
for(int i=1;i<=n;i++) s+=calc(num[i],d);
return s;
}
ll ans[100005];
ll query(ll x,ll y) {
return y*(x-y*y);
}
int main() {
int n;
ll k;
scanf("%d%lld",&n,&k);
ll l=inf,r=-inf;
for(int i=1;i<=n;i++) {
scanf("%lld",&num[i]);
l=min(l,F(num[i],num[i]-1));
r=max(r,F(num[i],0));
}
while (l<r) {
ll mid=((l+r)>>1);
if (check(n,mid)<=k) r=mid; else l=mid+1;
}
for(int i=1;i<=n;i++) {
ll t=calc(num[i],l);
k-=t;
ans[i]=t;
}
for(int i=1;i<=n;i++)
if (k&&ans[i]<num[i]&&F(num[i],ans[i])==l-1) {
ans[i]++;
k--;
}
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
printf("\n");
return 0;
}
E. Train Tracks
考虑对于每个非叶子站点
x
x
x,所有会经过它(不停在
x
x
x)的火车按经过
x
x
x的时间
t
i
t_i
ti排序可以得到若干个
(
t
i
,
e
i
)
(t_i,e_i)
(ti,ei),其中
e
i
e_i
ei是接下来要走的边,且有
t
0
=
0
t_0=0
t0=0,
e
0
e_0
e0为初始的边。显然,对于一对相邻的
(
t
i
−
1
,
e
i
−
1
)
(t_{i-1},e_{i-1})
(ti−1,ei−1)和
(
t
i
,
e
i
)
(t_i,e_i)
(ti,ei),若
e
i
−
1
≠
e
i
e_{i-1}\neq e_i
ei−1=ei,我们需要在
[
t
i
−
1
+
1
,
t
i
]
[t_{i-1}+1,t_i]
[ti−1+1,ti]的时间内对
x
x
x进行一次操作,否则会在时刻
t
i
t_i
ti爆炸。
如果我们把所有点对应的所有这些需要操作的时间区间
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri]都拿出来,并且按
L
i
L_i
Li排序,那么就可以做一个经典的贪心:依次扫描每个有用的时刻,用一个堆维护当前可以操作且还没有操作的区间对应的
R
R
R,每次取出最小的
R
R
R操作。如果某个时刻堆顶的
R
R
R小于当前时刻,第一问的答案即为这个
R
R
R,否则第一问的答案为
i
n
f
inf
inf(
−
1
-1
−1)。第二问的答案显然是小于第一问答案的
R
i
R_i
Ri数目。
现在问题在于怎么找出所有这种时间区间
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri],这是一个经典的数据结构问题:考虑从底往上启发式合并所有的火车的时间,用一个set维护顺序,因为我们只需要考虑
e
i
e_i
ei不同的,每次合并的时候只需要暴力枚举较小的set,查找它里面元素在合并后set的前驱后继即可,注意还要处理最开始的火车。这部分总共会找出
O
(
n
+
m
log
n
)
\mathcal O(n+m\log n)
O(n+mlogn)个时间区间。
时间复杂度为
O
(
n
log
n
+
m
log
2
n
)
\mathcal O(n\log n+m\log^2n)
O(nlogn+mlog2n)。
#include <bits/stdc++.h>
#define FR first
#define SE second
#define last last2
#define inf 0x3f3f3f3f3f3f3f3fLL
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pr;
vector <pr> e[100005];
ll dis[100005];
int dfn[100005],ed[100005],dfs_cnt;
void dfs1(int x) {
dfn[x]=++dfs_cnt;
for(int i=0;i<e[x].size();i++) {
int u=e[x][i].FR;
dis[u]=dis[x]+e[x][i].SE;
dfs1(u);
}
ed[x]=dfs_cnt;
}
pr num[10000005];
int cnt;
set <pr> q[100005];
vector <int> vt[100005];
inline bool issub(int x,int y) {
return dfn[x]<=dfn[y]&&ed[x]>=dfn[y];
}
void dfs2(int x) {
if (!e[x].size()) {
for(int i=0;i<vt[x].size();i++) q[x].insert(pr(vt[x][i],x));
return;
}
int son=e[x][0].FR;
for(int i=0;i<e[x].size();i++) {
int u=e[x][i].FR;
dfs2(u);
if (q[u].size()>q[son].size()) son=u;
}
swap(q[x],q[son]);
for(int i=0;i<e[x].size();i++)
if (e[x][i].FR!=son) {
int u=e[x][i].FR;
for(set<pr>::iterator it=q[u].begin();it!=q[u].end();it++)
q[x].insert(*it);
}
for(int i=0;i<e[x].size();i++)
if (e[x][i].FR!=son) {
int u=e[x][i].FR;
for(set<pr>::iterator it=q[u].begin();it!=q[u].end();it++) {
set<pr>::iterator it2=q[x].find(*it),it3=it2;
if (it2!=q[x].begin()) {
it2--;
if (!issub(u,it2->SE)) num[++cnt]=pr((it2->FR)+1+dis[x],(it->FR)+dis[x]);
}
it3++;
if (it3!=q[x].end()) {
if (issub(son,it3->SE)) num[++cnt]=pr((it->FR)+1+dis[x],(it3->FR)+dis[x]);
}
}
}
int last=e[x][e[x].size()-1].FR;
if (q[x].size()&&!issub(last,(q[x].begin())->SE)) num[++cnt]=pr(1,((q[x].begin())->FR)+dis[x]);
for(int i=0;i<vt[x].size();i++) q[x].insert(pr(vt[x][i],x));
}
priority_queue <ll,vector<ll>,greater<ll> > que;
ll solve() {
sort(num+1,num+cnt+1);
num[cnt+1]=pr(inf,0);
for(int i=1,j=1;i<=cnt;i=j+1) {
while (j<cnt&&num[j+1].FR==num[i].FR) j++;
for(int k=i;k<=j;k++) que.push(num[k].SE);
ll d=num[i].FR;
while (!que.empty()&&d<num[j+1].FR) {
ll x=que.top();que.pop();
if (x<d) return x;
d++;
}
}
return inf;
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[x].push_back(pr(y,z));
}
dfs1(1);
for(int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
vt[x].push_back(y);
}
dfs2(1);
ll ans1=solve();
int ans2=0;
for(int i=1;i<=cnt;i++)
if (num[i].SE<ans1) ans2++;
printf("%lld %d\n",(ans1<inf)?ans1:-1,ans2);
return 0;
}
F. Piet’s Palette
按套路,考虑找出mix操作中的不变量。构造一下,令
′
W
′
,
′
R
′
,
′
Y
′
,
′
B
′
'W','R','Y','B'
′W′,′R′,′Y′,′B′的权值分别为
0
,
1
,
2
,
3
0,1,2,3
0,1,2,3,那么每次操作后异或和不变。从这个角度也可以发现操作顺序是不会影响答案的,最后结果即为所有元素的异或和。
注意到异或和可以拆位,给每个位置
i
i
i定义两个变量
x
i
x_i
xi和
y
i
y_i
yi(
01
01
01变量),分别表示两个位的权值,也即每个mix操作相当于给定了若干个
x
i
x_i
xi的和与若干个
y
i
y_i
yi的和在
F
2
\mathbb F_2
F2下的方程。
考虑修改操作,RY相当于交换
x
i
x_i
xi和
y
i
y_i
yi,
R
B
RB
RB相当于让
y
i
y_i
yi变为
(
x
i
+
y
i
)
m
o
d
2
(x_i+y_i)\bmod \ 2
(xi+yi)mod 2,
Y
B
YB
YB相当于让
x
i
x_i
xi变为
(
x
i
+
y
i
)
m
o
d
2
(x_i+y_i) \bmod \ 2
(xi+yi)mod 2,那么任意时刻的
x
i
x_i
xi和
y
i
y_i
yi都可以由初始的
x
i
x_i
xi和
y
i
y_i
yi表示。
最后我们得到了
2
n
2n
2n个变量的
O
(
k
)
\mathcal O(k)
O(k)个在
F
2
\mathbb F_2
F2下的方程,可以直接高斯消元,用bitset加速即可。
时间复杂度为
O
(
n
k
+
n
2
k
w
)
\mathcal O(nk+\frac{n^2k}{w})
O(nk+wn2k)。
#include <bits/stdc++.h>
using namespace std;
typedef bitset<2005> bs;
bs a[2005];
int pos[2005];
int ans[2005];
bool gause(int n,int m) {
int d=1;
for(int i=1;i<=n;i++) {
if (!a[d][i]) {
for(int j=d+1;j<=m;j++)
if (a[j][i]) {
swap(a[d],a[j]);
break;
}
}
if (!a[d][i]) continue;
pos[d]=i;
for(int j=1;j<=m;j++)
if (j!=d&&a[j][i]) a[j]^=a[d];
d++;
}
for(int i=d;i<=m;i++)
if (a[i][n+1]) return 0;
for(int i=1;i<d;i++) ans[pos[i]]=a[i][n+1];
return 1;
}
bool num[1005][4];
int id[128],cur[1005];
const char *val=".RYB";
int main() {
id['W']=0;id['R']=1;id['Y']=2;id['B']=3;
int n,m;
scanf("%d%d",&n,&m);
int sz=0;
for(int i=1;i<=n;i++) num[i][0]=num[i][3]=1;
for(int i=1;i<=m;i++) {
char str[5];
int k;
scanf("%s%d",str,&k);
for(int j=1;j<=k;j++) scanf("%d",&cur[j]);
if (str[0]=='m') {
scanf("%s",str);
int v=id[str[0]];
++sz;
for(int j=1;j<=k;j++) {
int x=cur[j];
if (num[x][0]) a[sz].flip(2*x-1);
if (num[x][1]) a[sz].flip(2*x);
}
if (v&1) a[sz].flip(2*n+1);
++sz;
for(int j=1;j<=k;j++) {
int x=cur[j];
if (num[x][2]) a[sz].flip(2*x-1);
if (num[x][3]) a[sz].flip(2*x);
}
if ((v>>1)&1) a[sz].flip(2*n+1);
}
else if (str[0]=='R'&&str[1]=='Y') {
for(int j=1;j<=k;j++) {
int x=cur[j];
swap(num[x][0],num[x][2]);
swap(num[x][1],num[x][3]);
}
}
else if (str[0]=='R'&&str[1]=='B') {
for(int j=1;j<=k;j++) {
int x=cur[j];
num[x][2]^=num[x][0];
num[x][3]^=num[x][1];
}
}
else {
for(int j=1;j<=k;j++) {
int x=cur[j];
num[x][0]^=num[x][2];
num[x][1]^=num[x][3];
}
}
}
if (!gause(2*n,sz)) {
puts("NO");
return 0;
}
puts("YES");
for(int i=1;i<=n;i++) {
int s=(ans[2*i-1]^(ans[2*i]<<1));
putchar(val[s]);
}
printf("\n");
return 0;
}