牛客网比赛-Wannafly挑战赛27

版权声明:转载注明出处,谢谢,有问题可以向博主联系 https://blog.csdn.net/VictoryCzt/article/details/83443146

无关前置

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


题意简述

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

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


这个签到题吧,范围十分的小,值域才 1 0 5 10^5 ,开个数组记录一下每个数字出现次数,然后 n \sqrt{n} 的每次枚举平方就好啦,复杂度 O ( n n ) 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;
}

题意简述

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

数据范围

n 100000 , m 200000 n\leq 100000, m \leq 200000 n n 为点数, m m 为边数)


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

#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;
}

题意简述

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

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


这个就是个裸的树上背包问题,我们定义状态 f [ i ] [ j ] f[i][j] ,表示以 i i 为根的子树,连通块大小为 j j 的方案数,其中我们特殊规定当 j = 0 j=0 时,表示 i i 点不与上方父亲结点连通的方案数,所以就深搜一遍,树形 D P \rm DP ,复杂度看似最坏为 n 3 n^3 ,实际上由于每次枚举不是枚举所有的点数,所以复杂度大概算下来为 n 2 n 5 3 n^2\sim n^\frac{5}{3} 左右,是不会达到 n 3 n^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;
}

题意简述

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

  1. S S 中加入 x x
  2. 输出 y S g c d ( x , y ) k ( m o d   p ) \sum\limits_{y\in S}gcd(x,y)^k(\rm mod\ p)

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


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

我们每次 n \sqrt{n} 的统计因子,然后记最多因子个数为 w w ,每次 w 2 w^2 的统计答案,然后加一点常数优化和剪枝,就可以过了,复杂度 O ( n n × w 2 ) O(n\sqrt{n}\times w^2) ,但是注意这里的 w 2 w^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 , k n, k ,求一个长度为 n n 的数组 a a , 满足有恰好 k k 对数对 ( i , j ) ( 1 i &lt; j n ) (i, j) (1 \leq i &lt; j\leq n) 满足 a i + a j a_i + a_j 为完全平方数。如果不存在,输出 1 -1

数据范围: n 1 0 5 , k 1 0 10 n\leq 10^5,k\leq 10^{10}


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

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

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

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

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

#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;
}

题意简述

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

  1. p 1 . y &gt; p 2 . y &gt; p 3 . y p_1.y&gt;p_2.y&gt;p_3.y
  2. p 1 . x &gt; m a x ( p 2 . x , p 3 . x ) p_1.x&gt;max(p_2.x,p_3.x)

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

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


暴力就不说了。

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

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

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


End

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

猜你喜欢

转载自blog.csdn.net/VictoryCzt/article/details/83443146