- cout<<endl \texttt{cout<<endl} cout<<endl 有 fflush(stdout) \texttt{fflush(stdout)} fflush(stdout) 的作用。
- 注意最后输出答案不要写成 cout<<"! "<<solve()<<endl; \texttt{cout<<"! "<<solve()<<endl;} cout<<"! "<<solve()<<endl; 这样会先输出感叹号再进入函数。应该先令 ans=solve() \texttt{ans=solve()} ans=solve(),再输出 cout<<"! "<<ans<<endl; \texttt{cout<<"! "<<ans<<endl;} cout<<"! "<<ans<<endl;
AGC044D Guess the Password
全填一种字符可以确定该字符的个数。
两个串归并的时候通过判断第一个串的首字母加上第二串剩下的所有字母是否是原序列的子序列来确定下一位是否是第一个串的首字母。
CF1354G Find a Gift
注意到
k
≤
n
2
k\le \frac n2
k≤2n,那么我们可以判断第一个盒子是不是石头盒子,随机
x
x
x 次判断是否另一个盒子是否重于它,出错概率
(
1
2
)
x
(\frac 12)^x
(21)x
因为要找编号最小的,我们考虑倍增确定前面
2
k
2^k
2k 个都是石头盒子,在
[
2
k
+
1
,
2
k
+
1
]
[2^k+1,2^{k+1}]
[2k+1,2k+1] 这个区间内存在礼物盒子,可以二分,跟前面一段长度相同的石头盒子比较看重量是否相等。
Codechef GUESSPRM Guess the Prime!
判断
P
P
P 是不是 2,只需要询问
2
k
2^k
2k,余数为 0 就说明是 2.
余数不为 0,我们可以通过这种方式得到在模
P
P
P 意义下 2 的逆元:
令
x
=
2
2
k
−
2
2
k
%
P
x=2^{2k}-2^{2k}\%P
x=22k−22k%P,
x
x
x 要为正,
k
k
k 可以取
15
15
15
此时
P
∣
x
P|x
P∣x,去掉
x
x
x 中
2
2
2 的因子,得到
x
=
t
p
x=tp
x=tp,
t
t
t 是奇数,那么
i
n
v
2
≡
x
+
1
2
(
m
o
d
P
)
inv_2\equiv \frac {x+1}2\pmod P
inv2≡2x+1(modP)
但是这两个数并不相等,我们还需要再一次取模,令 y = ( x + 1 2 ) 2 % P y=(\frac {x+1}2)^2\%P y=(2x+1)2%P,那么 y = i n v 4 y=inv_4 y=inv4,即模 P P P 意义下 4 的逆元。
若
P
≡
1
(
m
o
d
4
)
P\equiv 1\pmod 4
P≡1(mod4),那么
i
n
v
4
=
3
P
+
1
4
inv_4=\frac {3P+1}4
inv4=43P+1,解得
P
=
4
y
−
1
3
P=\frac {4y-1}3
P=34y−1
若
P
≡
3
(
m
o
d
4
)
P\equiv 3\pmod 4
P≡3(mod4),那么
i
n
v
4
=
P
+
1
4
inv_4=\frac {P+1}4
inv4=4P+1,解得
P
=
4
y
−
1
P=4y-1
P=4y−1
当 P % 3 ≠ 0 P\%3\neq 0 P%3=0 或 y = 1 y=1 y=1 时,取第二式,否则取第一式。
Codechef GUESSG Guessing Game
不能两次都撒谎,意味着选两个点,两次都撒谎所取到的区间是可以去掉的(剩下的情况是可能出现的情况,会覆盖其余的区间)。
因此有个初步想法,先取中点,不妨设得到了
G
G
G,然后在
N
4
\frac N4
4N 处取点。
如果得到了
G
G
G,那么两次都撒谎所取到的区间是
[
1
,
N
4
)
[1,\frac N4)
[1,4N)
如果得到了
L
L
L,那么两次都撒谎所取到的区间是
(
N
4
,
N
2
)
(\frac N4,\frac N2)
(4N,2N)
这样每次可以去掉 1 4 \frac 14 41,总次数是 log 3 4 1 0 − 9 ∗ 2 ≈ 144 \log_{\frac 34}10^{-9}*2\approx144 log4310−9∗2≈144 次。
然后就是凭借人类智慧的时候了
官方题解的优化做法有点难受,请自行参阅:editorial
我根据观摩submissions加上一点无脑思考得到了一个比较舒服的方法:
先取
N
2
\frac N2
2N,假设得到
G
G
G,再取
x
x
x 处询问(
x
x
x 是个小于1/2的分数)
- 如果仍然得到 G G G,则可以去掉 x x x 的部分。
- 如果得到
L
L
L,那么只能去掉
(
x
,
N
2
)
(x,\frac N2)
(x,2N) 的部分,此时再取
3
N
4
\frac {3N}4
43N 处询问
- 如果得到 G G G,那么可以去掉红线标注的部分
- 如果得到
L
L
L,那么可以去掉蓝线标注的部分
第一种大情况中,用 2 次询问,去掉了
x
x
x,最坏次数为
log
1
−
x
1
0
−
9
∗
2
\log_{1-x}10^{-9}*2
log1−x10−9∗2
第二种大情况中,用 3 次询问,去掉了
3
4
−
x
\frac 34-x
43−x,最坏次数为
log
x
+
1
4
1
0
−
9
∗
3
\log_{x+\frac 14}10^{-9}*3
logx+4110−9∗3
当
x
≈
0.315865
x\approx 0.315865
x≈0.315865 时两式取等,次数约为 110 次。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
vector<pii>A;
int n;
int qry(int x){
cout<<x<<endl;
char s[3]; scanf("%s",s);
if(s[0]=='E') exit(0);
return s[0]=='G'?1:0;
}
int val(int k){
for(pii a:A){
if(k<=a.second-a.first+1) return a.first+k-1;
k-=a.second-a.first+1;
}
assert(0);
}
void remove(int l,int r){
static vector<pii>B; B.clear();
for(pii a:A)
if(a.second<l||a.first>r) B.push_back(a);
else{
if(a.first<l) B.push_back(pii(a.first,l-1));
if(a.second>r) B.push_back(pii(r+1,a.second));
}
A=B;
}
int main()
{
scanf("%d",&n);
A.push_back(pii(1,n));
while(n>=5){
int m=(n+1)>>1,v1=val(m),q1=qry(v1);
if(q1){
int x=1+(n-1)*0.315865,v2=val(x),q2=qry(v2);
if(q2) remove(1,v2);
else{
int y=n*0.75,v3=val(y),q3=qry(v3);
if(q3) remove(v2,v3);
else remove(v2,v1),remove(v3,1e9);
}
}
else{
int x=n*(1-0.315865),v2=val(x),q2=qry(v2);
if(!q2) remove(v2,1e9);
else{
int y=n*0.25,v3=val(y),q3=qry(v3);
if(!q3) remove(v3,v2);
else remove(1,v3),remove(v1,v2);
}
}
n=0;
for(pii a:A) n+=a.second-a.first+1;
}
for(int i=1;i<=n;i++) qry(val(i));
}
CF1336D Yui and Mahjong Set
首先给一个>1的数+1可以根据
t
r
i
p
l
e
t
triplet
triplet 的变化确定它的个数。
假设已经知道了
a
i
−
2
,
a
i
−
1
a_{i-2},a_{i-1}
ai−2,ai−1 ,前面的值现在为正,以及
a
i
a_i
ai 是否为 0,此时给
a
i
+
1
a_i+1
ai+1,就可以知道
a
i
a_i
ai,根据
Δ
s
t
r
a
i
g
h
t
=
(
a
i
−
2
+
1
)
(
a
i
−
1
+
1
)
+
(
a
i
−
1
+
1
)
a
i
+
1
+
a
i
+
1
a
i
+
2
\Delta straight=(a_{i-2}+1)(a_{i-1}+1)+(a_{i-1}+1)a_{i+1}+a_{i+1}a_{i+2}
Δstraight=(ai−2+1)(ai−1+1)+(ai−1+1)ai+1+ai+1ai+2 可以得知
a
i
+
1
a_{i+1}
ai+1 是否为 0.
并且在给
a
n
−
1
+
1
a_{n-1}+1
an−1+1 时可以得到
(
a
n
−
3
+
1
)
(
a
n
−
2
+
1
)
+
(
a
n
−
2
+
1
)
a
n
=
Δ
s
t
r
a
i
g
h
t
(a_{n-3}+1)(a_{n-2}+1)+(a_{n-2}+1)a_n=\Delta straight
(an−3+1)(an−2+1)+(an−2+1)an=Δstraight 从而解出
a
n
a_n
an
剩下就只需要用
≤
3
\le 3
≤3 次操作求出
a
1
,
a
2
a_1,a_2
a1,a2 就可以了。由于
a
3
a_3
a3 可能为 0 不太好做,考虑用
≤
4
\le 4
≤4 次操作求出
a
1
,
a
2
,
a
3
a_1,a_2,a_3
a1,a2,a3,进行这样的操作:
2
1
3
1
2~1~3~1
2 1 3 1,得到
a
1
a_1
a1
第二次可以得到
(
a
2
+
1
)
a
3
(a_2+1)a_3
(a2+1)a3,第四次可以得到
(
a
2
+
1
)
(
a
3
+
1
)
(a_2+1)(a_3+1)
(a2+1)(a3+1),从而解出
a
2
,
a
3
a_2,a_3
a2,a3
n = 4 n=4 n=4 的实现情况可以统一起来。
Code:
#include<bits/stdc++.h>
using namespace std;
int n,x[105],a[105],b[105],A,B,s[10005];
void ask(int i,int &a,int &b){
printf("+ %d\n",i),fflush(stdout);
int u,v; scanf("%d%d",&u,&v);
a=u-A,b=v-B,A=u,B=v;
}
int main()
{
scanf("%d%d%d",&n,&A,&B);
for(int i=1;i<=105;i++) s[i*(i-1)/2]=i;
ask(2,a[2],b[2]),ask(1,a[1],b[1]),ask(3,a[3],b[3]),ask(1,a[0],b[0]);
x[1]=s[a[0]]-1,x[2]=b[0]-b[1]-1,x[3]=b[1]/(x[2]+1);
for(int i=4;i<n;i++){
ask(i,a[i],b[i]);
if(b[i-1]-(x[i-2]+1)*(x[i-3]+1)) x[i]=s[a[i]];
}
x[n]=(b[n-1]-(x[n-3]+1)*(x[n-2]+1))/(x[n-2]+1);
printf("! ");
for(int i=1;i<=n;i++) printf("%d%c",x[i],i==n?10:32);
}
CF1372F Omkar and Modes
单调不降的数组(不给出), k k k 种值(不给出),可以询问区间,返回众数和出现次数,在 4 k 4k 4k 次询问之内确定这个数组。
luogu还没放上去呢。。
神仙题解
做法简单但是不会证的题解
看起来有点复杂的官方题解
[WC2018]即时战略
链的情况随机序列失败的次数是期望 O ( ln n ) O(\ln n) O(lnn) 的。相当于就是单调栈的期望大小,证明可以看这里:凸包上点个数期望
树的话要加速查找,可以用LCT二分是从链上哪个位置伸出去的,在splay上二分,存前驱和后继。
也可以动态点分治,在点分树上从上往下,每次找到explore的点在哪个点分子树内然后递归。
都可以做到 O ( n log n ) O(n\log n) O(nlogn),第二个可能稍微需要一点小trick,比如插入时直接插入一条链再重构,找在哪个子树用数组存下来。。
Code(点分,nlog^2n):
#include "rts.h"
#include<bits/stdc++.h>
#define maxn 300005
#define ADJ(v,u) for(int i=fir[u],v;i;i=nxt[i])
using namespace std;
int vis[maxn],tim;
int fa[maxn],dep[maxn],ch[75][maxn],rt,*pt[maxn],siz[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void getsiz(int u,int ff){
siz[u]=1;
ADJ(v,u) if(vis[v=to[i]]!=tim&&v!=ff) getsiz(v,u),siz[u]+=siz[v];
}
void getrt(int u,int ff,int tsz,int &g){
bool flg=(tsz-siz[u])<<1<=tsz;
ADJ(v,u) if(vis[v=to[i]]!=tim&&v!=ff){
getrt(v,u,tsz,g),flg&=siz[v]<<1<=tsz;
}
if(flg) g=u;
}
int getrt(int u){int x=0; return getrt(u,0,siz[u],x),x;}
void build(int u){
getsiz(u,0),vis[u]=tim;
ADJ(v,u) if(vis[v=to[i]]!=tim){
int t=getrt(v);
fa[t]=u,dep[t]=dep[u]+1,pt[t]=&(ch[dep[u]][v]=t);
build(t);
}
}
int a[maxn];
bool mark[maxn];
void extend(int x,int ed){while(x!=ed) mark[x=explore(x,ed)]=1;}
void play(int n,int T,int Type){
mark[1]=1;
for(int i=2;i<=n;i++) a[i]=i;
srand(555555),random_shuffle(a+2,a+1+n);
if(Type==3){
for(int i=2,l=1,r=1,x;i<=n;i++) if(!mark[a[i]]){
if(!mark[x=explore(l,a[i])]) mark[x]=1,extend(x,l=a[i]);
else extend(r,a[i]),r=a[i];
}
return;
}
siz[rt=1]=1,pt[1]=&rt;
for(int i=2;i<=n;i++) for(int u=rt,v;!mark[a[i]];){
if(mark[v=explore(u,a[i])]) u=ch[dep[u]][v];
else{
siz[v]=mark[v]=1,line(u,v),line(v,u);
fa[v]=u,dep[v]=dep[u]+1,pt[v]=&(ch[dep[u]][v]=v);
int p=0;
for(int y=v,x=u;x;x=fa[y=x]) if(++siz[x]*0.75<siz[y]) p=x;
if(p){
++tim; for(int x=fa[p];x;x=fa[x]) vis[x]=tim;
getsiz(p,0); int r=getrt(p);
*(pt[r]=pt[p])=r,fa[r]=fa[p],dep[r]=dep[fa[r]]+1;
build(r);
}
u=v;
}
}
}