非传统题集(交互,提答)

  • 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 k2n,那么我们可以判断第一个盒子是不是石头盒子,随机 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=22k22k%P x x x 要为正, k k k 可以取 15 15 15
此时 P ∣ x P|x Px,去掉 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 inv22x+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 P1(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=34y1
P ≡ 3 ( m o d 4 ) P\equiv 3\pmod 4 P3(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=4y1

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 log431092144 次。

然后就是凭借人类智慧的时候了

官方题解的优化做法有点难受,请自行参阅: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 log1x1092
第二种大情况中,用 3 次询问,去掉了 3 4 − x \frac 34-x 43x,最坏次数为 log ⁡ x + 1 4 1 0 − 9 ∗ 3 \log_{x+\frac 14}10^{-9}*3 logx+411093
x ≈ 0.315865 x\approx 0.315865 x0.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} ai2,ai1 ,前面的值现在为正,以及 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=(ai2+1)(ai1+1)+(ai1+1)ai+1+ai+1ai+2 可以得知 a i + 1 a_{i+1} ai+1 是否为 0.
并且在给 a n − 1 + 1 a_{n-1}+1 an1+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 (an3+1)(an2+1)+(an2+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;
		}
	}
}

UOJ上这个点分写法常数蛮小的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值