牛客网比赛-Wannafly挑战赛27

无关前置

最近同学都在打牛客网的比赛并且博主也在写一下牛客网的题,博主就去看了看,打了一场,题目质量还是非常不错的。我才不会告诉你我没开long long错了好久QWQ


题意简述

给出长度为nn的序列aa, 求有多少对数对 (i,j)(1i<jn)(i, j) (1 \leq i < j \leq n) 满足 ai+aja_i + a_j 为完全平方数。

数据范围:1n,a1051\leq n,a\leq10^5


这个签到题吧,范围十分的小,值域才10510^5,开个数组记录一下每个数字出现次数,然后n\sqrt{n}的每次枚举平方就好啦,复杂度O(nn)O(n\sqrt{n})

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=3e5+10;
int n,maxv;ll ans;
int A[M],B[M];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&A[i]),maxv=max(A[i],maxv);
	for(int i=1;i<=n;i++){
		for(int j=1;j*j<=(maxv<<1);j++){
			if(j*j-A[i]<0) continue;
			ans+=B[j*j-A[i]];
		}
		++B[A[i]];
	}
	printf("%lld\n",ans);
	return 0;
}

题意简述

给出一棵仙人掌(每条边最多被包含于一个环,无自环,无重边,保证连通),要求用最少的颜色对其顶点染色,满足每条边两个端点的颜色不同,输出最小颜色数即可

数据范围

n100000,m200000n\leq 100000, m \leq 200000nn为点数,mm为边数)


这个稍微推一下就知道了,本来这个问题在一般的无向图上,是非常难解决的,但是我们看,这是一个仙人掌,只有简单环。所以对于所有的环,如果有奇数点环,那么至少用三种颜色;如果所有环点数都为偶数,那么至少用两种颜色。由于是简单环,深搜一遍即可统计答案。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int M=2e5+10;
int n,m;
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
bool vis[M];
int dfn[M],tim,ans;
void dfs(int a,int b){
	if(ans==3) return;
	dfn[a]=++tim;vis[a]=1;
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		if(!vis[g[i].to]){
			dfs(g[i].to,a);
		}else{
			int now=dfn[a]-dfn[g[i].to]+1;
			if((now&1)&&ans<3)ans=3;
			if(!(now&1)&&!ans)ans=2;
		}
	}
}
int a,b;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	if(!n){puts("0");return 0;}
	if(n==1){puts("1");return 0;}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){dfs(i,i);if(ans>=3) break;}
	}
	printf("%d\n",ans);
	return 0;
}

题意简述

给出一棵nn个点的树,求有多少种删边方案,使得删后的图每个连通块大小小于等于kk,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对998244353998244353取模

数据范围:2n,k20002\leq n,k\leq 2000


这个就是个裸的树上背包问题,我们定义状态f[i][j]f[i][j],表示以ii为根的子树,连通块大小为jj的方案数,其中我们特殊规定当j=0j=0时,表示ii点不与上方父亲结点连通的方案数,所以就深搜一遍,树形DP\rm DP,复杂度看似最坏为n3n^3,实际上由于每次枚举不是枚举所有的点数,所以复杂度大概算下来为n2n53n^2\sim n^\frac{5}{3}左右,是不会达到n3n^3的,所以完全能够(具体的一些证明可以去网上找一些树上背包的题的题解,里面可能有)。

所以直接打就好了,不要害怕超时。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k;
const int N=4010;
const int Mod=998244353; 
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[N<<1];
int head[N],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
int dp[N][N],sze[N],sum[N],ls[N];
void dfs(int a,int b){
	sze[a]=1;
	dp[a][1]=1;
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		dfs(g[i].to,a);
		for(int x=min(sze[a]+sze[g[i].to],k);x>=0;x--)ls[x]=0;
		for(int x=min(sze[a],k);x>=1;x--){
			for(int y=min(sze[g[i].to],k-x);y>=0;y--){
				ls[x+y]=(ls[x+y]+1ll*dp[a][x]*dp[g[i].to][y]%Mod)%Mod;
			}
		}
		sze[a]+=sze[g[i].to];
		for(int x=min(sze[a],k);x>=0;x--)dp[a][x]=ls[x];
	}
	for(int i=min(sze[a],k);i>=1;i--)dp[a][0]=(dp[a][0]+dp[a][i])%Mod;
}
int ans,a,b;
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	dfs(1,0);
	printf("%d\n",dp[1][0]);
	return 0;
}

题意简述

给你一个空的可重集,nn次操作,每次操作给出x,k,px,k,p,执行以下操作:

  1. SS中加入xx
  2. 输出ySgcd(x,y)k(mod p)\sum\limits_{y\in S}gcd(x,y)^k(\rm mod\ p)

1n,x,k,p1051\leq n,x,k,p\leq 10^5


显然暴力就为O(n2logn)O(n^2logn)
但是我们这里考虑,gcdgcd为最大公约数,所以一定为给出数字的因子,而在10510^5内的一个数字的因子数量不会很多(可以自己用线性筛筛一遍看看),所以我们考虑将每个插入的数拆成因子插入,然后我们从大到小枚举它的因子,因为对于一个它的因子,如果当前枚举的肯定是较大的,当这个因子有的时候,那么肯定会成为答案,记这个因子为aa,出现个数为cc,那么贡献为c×(ak)c\times (a^k),然后统计完这个我们需要把这个因子的因子的个数全部减去cc,因为包含该因子的数字我们已经算了,不能重复算,而后面只会枚举比它小的数,所以只用减去它的因子的数。

我们每次n\sqrt{n}的统计因子,然后记最多因子个数为ww,每次w2w^2的统计答案,然后加一点常数优化和剪枝,就可以过了,复杂度O(nn×w2)O(n\sqrt{n}\times w^2),但是注意这里的w2w^2是远远达不到的。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int N=1e5+10;
int n,p;
int cnt[N],del[N];
vector <int> vec[N];
int fpow(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=(1ll*a*a)%p){
		if(b&1)ans=(1ll*ans*a)%p;
	}
	return ans%p;
}
void add(int x){
	int a=sqrt(x);
	if(!vec[x].size()){
		for(int i=1;i<=a;i++){
			if(!(x%i)){
				vec[x].push_back(i);
				++cnt[i];
				if((x/i)!=i){
					vec[x].push_back(x/i);
					++cnt[x/i];
				}
			}
		}
	}else{
		for(int i=0,sz=vec[x].size();i<sz;i++)++cnt[vec[x][i]];
	}
}
void calc(int x){
	int a=sqrt(x);
	for(int i=1;i<=a;i++){
		if(!(x%i)){
			vec[x].push_back(i);
			if((x/i)!=i){
				vec[x].push_back(x/i);
			}
		}
	}
}
bool is_sort[N];
int query(int x,int k){
	memset(del,0,sizeof(del));
	int ans=0;
	if(!is_sort[x])is_sort[x]=1,sort(vec[x].begin(),vec[x].end());
	for(int i=vec[x].size()-1;i>=0;i--){
		int a=vec[x][i];
		if(cnt[a]<=del[a]) continue;
		ans=(ans+(cnt[a]-del[a])*fpow(a,k)%p)%p;
		if(!vec[a].size())calc(a);int now=cnt[a]-del[a];
		if(i)for(int j=0,sz=vec[a].size();j<sz;j++){
			if(vec[a][j]>a) break;
			del[vec[a][j]]+=now;
			if(i&&vec[a][j]==vec[x][i-1]&&cnt[vec[x][i-1]]<=del[vec[x][i-1]])--i;
		}
	}
	return ans;
}
int x,k;
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld%lld",&x,&k,&p);
		add(x);
		cout<<query(x,k)<<'\n';
	}
	return 0;
}

题意简述

给出 n,kn, k,求一个长度为 nn 的数组 aa, 满足有恰好 kk 对数对 (i,j)(1i&lt;jn)(i, j) (1 \leq i &lt; j\leq n)满足 ai+aja_i + a_j为完全平方数。如果不存在,输出 1-1

数据范围: n105,k1010n\leq 10^5,k\leq 10^{10}


这个题非常的良心啊,第一题的标程就是它的检验器啊。

我们首先分析,对于无解的情况,当你无论如何都凑不到kk个时就无解,所以对于一个长度为nn的序列,它最多能组合出n×(n1)2\frac{n\times(n-1)}{2}个完全平方数,所以当k&gt;n×(n1)2k&gt;\frac{n\times(n-1)}{2}我们就可以判断它无解。

然后对于有解的情况,由于只让输出一种方案,所以我们特殊构造即可,首先我们找几个数字xxxx要满足2x2x为完全平方数,然后因为数字可以重复,所以如果我们放了iixx,那么它就会有i×(i1)2\frac{i\times(i-1)}{2}的贡献。

所以我们分类讨论,对于kk可以写成i×(i1)2\frac{i\times(i-1)}{2}的形式的情况,我们直接输出iixx,然后找一个yy,满足2y2yx+yx+y都不为完全平方数,剩下的nin-i个数字由这个数字填充即可。

然后对于kk不能写成i×(i1)2\frac{i\times(i-1)}{2}的形式的情况,我们可以多选几种xx,然后先凑成近似kk,剩下的我们找一个ww,满足w+其中一个xw+\text{其中一个}x为完全平方数,而2w2w不为完全平方数,那么它就可以贡献出选的那个xx的个数那么多的完全平方数,不难发现,这样一定能凑到kk,剩下的同理选一个和其它已经选的xx,还有自己都不能组成完全平方数的数字填充即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1e5+10;
ll n,k,top,p;
ll ans[M];

int main(){
	scanf("%lld%lld",&n,&k);
	if(n*(n-1)/2<k){puts("-1");return 0;}
	for(;top*(top-1)/2<=k;++top);--top;
	ll res=k-top*(top-1)/2;
	if(!res){
		for(int i=1;i<=top;i++)ans[++p]=2;
		while(p<n)ans[++p]=3;
	}else{
		for(int i=1;i<=top-res;i++)ans[++p]=98;
		for(int i=1;i<=res;i++)ans[++p]=2;
		ans[++p]=7;
		while(p<n)ans[++p]=5;
	}
	for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
	return 0;
}

题意简述

一个空的二维平面上,每次加入或删除一个整点。
求每次操作完之后满足以下条件的三个点p1,p2,p3p_1,p_2,p_3的对数。

  1. p1.y&gt;p2.y&gt;p3.yp_1.y&gt;p_2.y&gt;p_3.y
  2. p1.x&gt;max(p2.x,p3.x)p_1.x&gt;max(p_2.x,p_3.x)

令操作数为nn,保证n60000,1x,ynn\leq 60000,1\leq x,y\leq n
保证加入点的时候平面上没有该点。
保证删除点的时候平面上有该点。

这个题由于有点难写以及比赛已经结束就没有写QWQ,所以这里口胡一下


暴力就不说了。

我们首先看统计的条件,十分特别,对于一个点,我们把它当做p1p_1,那么就只统计它的左下方的点对,且满足p2.y&gt;p3.yp_2.y&gt;p_3.y即可。

所以我们先用离线,排序,扫描线预处理,然后用三维或者二维偏序之类的统计答案即可。

比赛Rank1后面是切了这个题的,牛客网的代码是公开的,所以想看代码的可以去牛客网看。


End

没有抽到T桖QAQ,虽然冬天,但是还是想要啊QWQ

posted @ 2018-10-27 08:16  VictoryCzt  阅读(299)  评论(0编辑  收藏  举报