zoukankan      html  css  js  c++  java
  • lq的膜你赛

    题面

    CSP-S 2019膜你赛

    liqing

    A

    (a.cpp/in/out,1s,128MiB)

    lxjlxj因为是一个立青,所以他对所有的知识一窍不通。

    一天,他遇到了一道题,但他肯定不会啊,所以作为神犇的你能帮帮他吗?

    给你一个nn个数的序列。lxjlxj会选择一个数KK,然后对这个序列进行若干次操作。一次操作是在这个序列中选择恰好KK个互不相同的数,并将它们从序列中删掉(之后就不能再选了)。

    由于lxjlxj很无聊,所以他想知道对于每个K=1,2,3,...,nK=1,2,3,...,n,他最多能进行多少次操作。

    输入格式

    第一行仅一个正整数nn

    第二行给出一个长度为nn的序列,保证其中每个整数[1,n]in[1,n]

    输出格式

    nn行,每一行仅一个整数。第ii行的整数代表着当K=iK=i时的答案

    样例输入

    3
    2 1 2
    

    样例输出

    3
    1
    0
    

    数据范围与约定

    对于20%20\%的数据:n<=10n<=10

    对于50%50\%的数据:n<=2000n<=2000

    对于70%70\%的数据:n<=2105n<=2*10^5

    对于100%100\%的数据:n<=106n<=10^6

    B

    (b.cpp/in/out,1s,128MiB)

    lxjlxj并没有听懂你上一题的解法,所以就自闭了。于是他想找些谜语来快乐一下,你听到他的要求后就给了他n+mn+m道谜语,这些谜语的答案要不是yesyes,要不是nono

    但是由于lxjlxj是个立青,所以他一题都不会做,只能瞎猜。。。你看他太过可怜,就告诉了他其中有nn道题目答案是yesyesmm道题目答案是nono,并且每当lxjlxj回答完一道问题之后你就会马上告诉他这道题的正确答案。

    现在lxjlxj想通过你的帮助来最大化他答对题目的数目,你能告诉他如果按最优策略的话他答对题目数目的期望值吗?

    输入格式

    仅一行两个正整数nnmm

    输出格式

    仅一行一个整数,表示期望值对998244353998244353取模后的结果

    样例输入

    1 1
    

    样例输出

    499122178
    

    提示

    样例解释:

    第一个问题lxjlxj50%50\%的概率答对。而当他打完后,由于他已经知道了第一个问题的正确答案了,并且只有两个问题,所以第二个问题他有100%100\%的概率答对。

    所以输出的真实值是12+1=32frac{1}{2}+1=frac{3}{2}

    数据范围与约定

    对于20%20\%的数据:n+m<=10n+m<=10

    对于50%50\%的数据:n,m<=5000n,m<=5000

    对于60%60\%的数据:min(n,m)<=5000min(n,m)<=5000

    另有20%20\%的数据:n,m<=5105n=mn,m<=5*10^5,n=m

    对于100%100\%的数据:n,m<=5105n,m<=5*10^5

    C

    (c.cpp/in/out,2s,256MiB)

    lxjlxj因为是一个立青,所以他对所有的知识一窍不通。

    一天,他遇到了一道题,但他肯定不会啊,所以作为神犇的你能帮帮他吗?

    给你一棵以11号节点为根的树,每个点有一个点权aia_i

    qq次询问,每次询问给出两个数xxyy,要你求xxyy路径上最大的ai xor dis(i,y)a_i xor dis(i,y),保证xxyy的祖先。(xorxor是异或的意思,dis(a,b)dis(a,b)aabb简单路径的边数)

    输入格式

    第一行包含两个正整数nnqq,分别代表了这棵树的点数和总询问次数

    第二行有nn个整数,第ii个整数代表着aia_i。保证其中每个整数[0,n]in[0,n]

    接下来n1n-1行描述了一棵树。每一行包含两个整数xxyy(1x,yn)(1leq x,yleq n),代表着树上的一条边

    最后qq行,每一行包含两个整数xxyy(1<=x,y<=n)(1<=x,y<=n),代表着一次询问。保证xxyy的祖先

    输出格式

    qq行,第ii行代表着第ii次询问的答案

    样例输入

    5 3
    0 3 2 1 4
    1 2
    2 3
    3 4
    3 5
    1 4
    1 5
    2 4
    

    样例输出

    3
    4
    3
    

    数据范围与约定

    对于100%100\%的数据:n5104,q1.5105nleq 5*10^4,qleq 1.5*10^5

    Task1(20%)Task1(20\%)n,q5000n,qleq 5000

    Task2(30%)Task2(30\%):保证aia_i22的整数次幂

    Task3(10%)Task3(10\%):保证图是一条链

    Task4(20%)Task4(20\%):无​

    Task5(20%)Task5(20\%):保证所有的aia_i都相同

    解题报告

    (爆零记)

    A

    这道题我在考场上一眼就看出来是一道贪心题,可是考场上始终想不到正解。
    最终迫不得已,写了一个O(n2log(n))O(n^2*log(n))的做法,先贴一下吧——真的很好写。

    #include<cstdio>
    #include<vector>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    #define R register
    #define g getchar()
    using namespace std;
    const int N=1e6+10;
    void qr(int &x) { 
    	char c=g;x=0;
    	while(!isdigit(c))c=g;
    	while(isdigit(c))x=x*10+c-'0',c=g;
    }
    void write(int x) {
    	if(x/10) write(x/10);
    	putchar(x%10+'0');
    }
    vector<int>a;
    int n,cnt[N],c[N],top,ans[N];
    bool cmp(int x,int y) {return x>y;}
    int main() {
    	freopen("a.in","r",stdin);
    	freopen("a.out","w",stdout);
    	qr(n);
    	for(R int i=1,x;i<=n;i++)
    		qr(x),cnt[x]++;
    	for(R int i=1;i<=n;i++)
    		if(cnt[i])
    			c[++top]=cnt[i];
    	sort(c+1,c+top+1,cmp); ans[1]=n; ans[top]=c[top];
    	for(R int i=2;i<top;i++) {
    		a.clear();
    		for(R int j=1;j<=i;j++)a.push_back(c[j]);
    		for(R int j=i+1;j<=top;j++) {
    			ans[i]+=a[i-1];
    			for(int k=0;k<i-1;k++)a[k]-=a[i-1];
    			a.pop_back();
    			a.insert(lower_bound(a.begin(),a.end(),c[j],cmp),c[j]);
    		}
    		ans[i]+=a[i-1];
    	}
    	for(R int i=1;i<=n;i++)
    		write(ans[i]),puts("");
    	return 0;
    }
    

    正解——果然贪心。
    显然,只有每个数的出现次数有效(并不在意数是多少)
    anskans_k为第k个答案(输出值),则容易发现ansk>=ansk+1ans_k>=ans_{k+1}
    (单次取更多数的话,能进行的次数不会更多)

    所以我们可以倒着求答案,因为倒着来答案非降,所以如果可以在O(1)O(1)判断一个数可不可以成为答案的话,就可以O(n)O(n)求出所有答案了。

    本题的重点在于如何判断对于一个确定的kk,是否能执行tt次操作。
    在这里插入图片描述
    我们现在需要统计一下这条水平线下有多少个有用的单位格子(1*1)(表示每一个数)

    怎么算呢?——如果一个数出现次数为cntcnt,那么1cnt1sim cnt行的格子数就会增加1.最后做一个前缀和,就可以求出每条水平线下的格子数了。

    重要引理

    如果上述水平线下有cc个格子的话,那么能进行至少tt次操作,当且仅当ctkcge t*k

    这个引理的必要性显然,我们只需要对其充分性进行证明。

    证明:
    物理是个好学科。
    我们把水平线下的数按序排成一排,如1,1,2,3,31,1,2,3,3.
    假设现在有kk个桶,高度为tt
    在这里插入图片描述
    如图t=3t=3,我们把数顺序扔入桶中,数由于重力的作用,会沉到底部,当桶满后,扔入下一个桶。
    由于每个数都至多出现tt次,数一定可以塞满kk个桶,所以塞完以后取tt次整行就一定能够互不重复。

    显然,可以发现这样cc个数是能够被充分利用的,所以证毕。

    代码:

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    #define R register
    #define g getchar()
    using namespace std;
    typedef long long ll;
    const int N=1e6+10;
    void qr(int &x) { 
    	char c=g;x=0;
    	while(!isdigit(c))c=g;
    	while(isdigit(c))x=x*10+c-'0',c=g;
    }
    void write(int x) {
    	if(x/10) write(x/10);
    	putchar(x%10+'0');
    }
    int n,cnt[N],ans,a[N];
    ll c[N];
    int main() {
    	freopen("a.in","r",stdin);
    	freopen("a.out","w",stdout);
    	qr(n);
    	for(int i=1,x;i<=n;i++) {
    		qr(x);c[++cnt[x]]++;
    	}
    	for(int i=2;i<=n;i++) c[i]+=c[i-1];
    	for(int i=n; i; i--) {
    		while(c[ans+1]>=(ll)(ans+1)*i) 
    			ans++;
    		a[i]=ans;
    	}
    	for(int i=1;i<=n;i++) write(a[i]),puts("");
    	return 0;
    }
    	
    

    B

    思路来源
    首先,很容易想到O(n2)O(n^2)的暴力概率DP的做法。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=5010,mod=998244353;
    int f[N][N],inv[N<<1];
    ll dfs(int n,int m) {//n<=m
    	if(f[n][m]!=-1) return f[n][m];
    	ll t=(ll)m*inv[n+m]%mod;int &ans=f[n][m];
    	if(n==m) ans=(t+dfs(n-1,n))%mod;
    	else ans=(t*(dfs(n,m-1)+1)%mod+((ll)n*inv[n+m]%mod)*dfs(n-1,m)%mod)%mod;
    	return ans;
    }
    int main() {
    	freopen("b.in","r",stdin);
    	freopen("b.out","w",stdout);
    	int n,m,s; scanf("%d %d",&n,&m);s=n+m;
    	inv[1]=1;for(int i=2;i<=s;i++) inv[i]=(ll)inv[mod%i]*(mod-mod/i)%mod;
    	if(n>m)swap(n,m);
    	memset(f,-1,sizeof f); f[0][0]=0;
    	printf("%lld
    ",dfs(n,m));return 0;
    }
    
    

    由这个暴力思路,我们可以进一步优化。
    把整个状态空间抽象成一个nmn*m的矩形(具体来讲,左上角为(n,m)(n,m),右下角为(0,0)(0,0).)
    那么整个问题其实就是相当于一个从左上角走到右下角,途中只能向下或向右走的问题。
    在这里插入图片描述
    我们要仔细研究一下——这道题跟对角线y=xy=x有着密切的关系
    (为什么,因为只用对角线上的选择倾向是不固定的——当两种题目的剩余数量相同时,显然猜中的概率为12dfrac{1}{2}
    定义函数f(n,m,v)=max(n,m)vf(n,m,v)=max(n,m)-v,表示从(n,m)(v,v)(n,m)走到(v,v)的期望猜中题数。
    我们假设从(n,m)线(v,v)(n,m)走到的第一个对角线上的点为(v,v),则f(n,m,v)期望答对f(n,m,v)道题
    假设下一个经过点为(u,u)(u,u),则有期望答对vuv-u道题。
    裂项相消,期望答对数=max(n,m)v+vu+ua+ab+b(线)0=max(n,m)0=max(n,m)-v+v-u+u-a+a-b+b(其他对角线上的点)……-0=max(n,m)-0

    好像有点不太对劲,对——这种感觉没错。
    因为对角线上的点(除原点),答对一道题的期望为12dfrac{1}{2},所以我们只要把所有路径经过对角线(除原点)的次数的期望ww求出来就行。

    最终答案就是max(n,m)+12wmax(n,m)+frac{1}{2}w.

    代码很短:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=5e5+10,mod=998244353;
    int jc[N<<1],inv[N<<1],n,m,s,ans;
    ll power(ll a,ll b) {
    	ll c=1;
    	while(b) {
    		if(b&1) c=c*a%mod;
    		a=a*a%mod; b=b>>1;
    	}
    	return c;
    }
    int C(int n,int m) {
    	return (ll)jc[n]*inv[m]%mod*inv[n-m]%mod;
    }
    int main() {
    	freopen("b.in","r",stdin);
    	freopen("b.out","w",stdout);
    	scanf("%d %d",&n,&m); s=n+m; if(n>m) swap(n,m);
    	jc[0]=1; for(int i=1;i<=s;i++) jc[i]=(ll)jc[i-1]*i%mod;
    	inv[s]=power(jc[s],mod-2); for(int i=s; i; i--) inv[i-1]=(ll)inv[i]*i%mod;
    	ans=0;
    	for(int i=1;i<=n;i++) ans=(ans+(ll)C(2*i,i)*C(s-2*i,m-i)%mod)%mod;
    	ans=(ll)ans*power(C(s,n)<<1,mod-2)%mod;
    	printf("%d
    ",(ans+m)%mod);
    	return 0;
    }
    
    

    C

    考场上一脸懵逼~

    观察柿子aixordis(i,y)a_i operatorname{xor} dis(i,y),因为异或是二进制上的运算,所以我们考虑把dis(i,y)dis(i,y)进行拆分(谁想得到啊 )。
    因为dis(i,y)[0,n)dis(i,y)in [0,n),所以我们把它分为两半,二进制下,每一段的位数lglglog(n)+1lfloorlog(n) floor+1.
    比方说,对于极限数据50000,我们把每一段定为8位。
    定义块长tt2lg2^{lg}(没错,就是分块!)

    dis(i,y)=dis(i,z)+jt(zdis(i,z)+1<t,dis(z,y)=jt,j[0,t))dis(i,y)=dis(i,z)+j*t(z是树上路径上的一个点,dis(i,z)+1<t,dis(z,y)=j*t,jin [0,t) ).
    那么原柿子就转换为aixor(dis(i,z)xorjt)=(aixordis(i,z))xorjta_i operatorname{xor} (dis(i,z) operatorname{xor} j*t)=(a_i operatorname{xor} dis(i,z)) operatorname{xor} j*t
    因为tt为二的次幂,所以异或jtj*t对前面运算结果的后lglg位没有任何影响。
    对于前lglg位呢,在TrieTrie树上跑最大异或和即可。

    算法思路:
    预处理出让每个点充当上面的zz,对于所有j[0,t)jin[0,t)的答案。
    查询的时候,我们大段直接跳,边角暴力统计即可。

    细节有点多,主要看代码:

    #include<cmath>
    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    #define g getchar()
    using namespace std;
    const int N=50010,T=260;
    void qr(int &x) {
    	char c=g;x=0;
    	while(!isdigit(c))c=g;
    	while(isdigit(c))x=x*10+c-'0',c=g;
    }
    void write(int x) {
    	if(x/10) write(x/10);
    	putchar(x%10+'0');
    }
    
    int n,m,val[N],lg,t,mx[N][T],f[N][T];
    
    int tot,trie[N<<3][2];//Trie
    void ins(int x) {
    	int p=0;
    	for(int i=lg-1;i>=0;i--) {
    		int c=x>>i&1;
    		if(!trie[p][c]) trie[p][c]=++tot;
    		p=trie[p][c];
    	}
    }
    int ask(int x,int y) {//值为x,y为正在预处理的点
    	int p=0,ret=0,q=0;//p为Trie树上指针,ret为前lg位的最大异或值,q为与x异或的值
    	for(int i=lg-1;i>=0;i--) {
    		int c=x>>i&1;
    		if(trie[p][c^1]) ret|=1<<i,q|=(c^1)<<i,p=trie[p][c^1];
    		else q|=c<<i,p=trie[p][c];
    	}
    	return (ret<<lg)|mx[y][q];
    }
    
    struct edge {int y,next;}a[N<<1];int len,last[N];
    void ins(int x,int y) {a[++len]=(edge){y,last[x]};last[x]=len;}
    
    int fa[N],dep[N],top[N],vis[T];//fa为父亲,dep为深度,top为跳t次后的父亲 
    void cmax(int &x,int y) { x<y?x=y:0;}
    
    void dfs(int x) {
    	if(dep[x]>=t) {
    		memset(trie,0,(tot+1)<<3); tot=0;//部分memset好 
    		int y,d;
    		for(y=x,d=0;d<t;d++,y=fa[y]) {//d为深度差 
    			cmax(mx[x][val[y]>>lg],(val[y]^d)&(t-1));//mx[i][j]表示以i为链底,前lg位为j的最大后lg位的值。 
    			if(vis[val[y]>>lg]!=x)vis[val[y]>>lg]=x,ins(val[y]>>lg);//减少重复进Trie树 
    		}
    		top[x]=y;
    		for(int i=0;i<t;i++) f[x][i]=ask(i,x);//预先记录,这样就不用浪费Trie树上的内存了——否则要记录很多东西 
    	}
    	for(int k=last[x];k;k=a[k].next) {
    		int y=a[k].y;if(y==fa[x])continue;
    		dep[y]=dep[x]+1; fa[y]=x; dfs(y);
    	}
    }
    
    int main() { 
    //	freopen("c.in","r",stdin);
    //	freopen("c.out","w",stdout);
    	qr(n); qr(m); lg=(log2(n)/2.0)+1; t=1<<lg;//lg这样算能保证t^2>=n-1 
    	for(int i=1;i<=n;i++) qr(val[i]);
    	for(int i=1,x,y;i<n;i++)
    		qr(x),qr(y),ins(x,y),ins(y,x);
    	dep[1]=1;dfs(1);
    	while(m--) {
    		int x, y, ans=0, d=0; 
    		for(qr(x),qr(y);dep[y]-dep[x]+1>=t;y=top[y],d++) cmax(ans,f[y][d]);//大段跳 
    		for(d <<= lg;y!=fa[x];y=fa[y],d++) cmax(ans,val[y]^d);//局部暴力 
    		write(ans); puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    Java编程之路相关书籍
    JAVA中的Random()函数
    在鼠标右键上加入使用notepad++编辑
    更改IE浏览器的收藏夹位置
    Java四类八种数据类型
    成为Java顶尖程序员 ,看这11本书就够了
    面向对象(多异常的声明与处理)
    面向对象(异常try-catch)
    面向对象(异常概述)
    面向对象(局部内部类和匿名内部类)
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373870.html
Copyright © 2011-2022 走看看