牛客网比赛-Wannafly挑战赛27
无关前置
最近同学都在打牛客网的比赛并且博主也在写一下牛客网的题,博主就去看了看,打了一场,题目质量还是非常不错的。我才不会告诉你我没开long long错了好久QWQ
题意简述
给出长度为的序列, 求有多少对数对 满足 为完全平方数。
数据范围:
这个签到题吧,范围十分的小,值域才,开个数组记录一下每个数字出现次数,然后的每次枚举平方就好啦,复杂度
#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;
}
题意简述
给出一棵仙人掌(每条边最多被包含于一个环,无自环,无重边,保证连通),要求用最少的颜色对其顶点染色,满足每条边两个端点的颜色不同,输出最小颜色数即可
数据范围
(为点数,为边数)
这个稍微推一下就知道了,本来这个问题在一般的无向图上,是非常难解决的,但是我们看,这是一个仙人掌,只有简单环。所以对于所有的环,如果有奇数点环,那么至少用三种颜色;如果所有环点数都为偶数,那么至少用两种颜色。由于是简单环,深搜一遍即可统计答案。
#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;
}
题意简述
给出一棵个点的树,求有多少种删边方案,使得删后的图每个连通块大小小于等于,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对取模
数据范围:
这个就是个裸的树上背包问题,我们定义状态,表示以为根的子树,连通块大小为的方案数,其中我们特殊规定当时,表示点不与上方父亲结点连通的方案数,所以就深搜一遍,树形,复杂度看似最坏为,实际上由于每次枚举不是枚举所有的点数,所以复杂度大概算下来为左右,是不会达到的,所以完全能够(具体的一些证明可以去网上找一些树上背包的题的题解,里面可能有)。
所以直接打就好了,不要害怕超时。
#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;
}
题意简述
给你一个空的可重集,次操作,每次操作给出,执行以下操作:
- 在中加入
- 输出
显然暴力就为
但是我们这里考虑,为最大公约数,所以一定为给出数字的因子,而在内的一个数字的因子数量不会很多(可以自己用线性筛筛一遍看看),所以我们考虑将每个插入的数拆成因子插入,然后我们从大到小枚举它的因子,因为对于一个它的因子,如果当前枚举的肯定是较大的,当这个因子有的时候,那么肯定会成为答案,记这个因子为,出现个数为,那么贡献为,然后统计完这个我们需要把这个因子的因子的个数全部减去,因为包含该因子的数字我们已经算了,不能重复算,而后面只会枚举比它小的数,所以只用减去它的因子的数。
我们每次的统计因子,然后记最多因子个数为,每次的统计答案,然后加一点常数优化和剪枝,就可以过了,复杂度,但是注意这里的是远远达不到的。
#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;
}
题意简述
给出 ,求一个长度为 的数组 , 满足有恰好 对数对 满足 为完全平方数。如果不存在,输出 。
数据范围:
这个题非常的良心啊,第一题的标程就是它的检验器啊。
我们首先分析,对于无解的情况,当你无论如何都凑不到个时就无解,所以对于一个长度为的序列,它最多能组合出个完全平方数,所以当我们就可以判断它无解。
然后对于有解的情况,由于只让输出一种方案,所以我们特殊构造即可,首先我们找几个数字,要满足为完全平方数,然后因为数字可以重复,所以如果我们放了个,那么它就会有的贡献。
所以我们分类讨论,对于可以写成的形式的情况,我们直接输出个,然后找一个,满足和都不为完全平方数,剩下的个数字由这个数字填充即可。
然后对于不能写成的形式的情况,我们可以多选几种,然后先凑成近似,剩下的我们找一个,满足为完全平方数,而不为完全平方数,那么它就可以贡献出选的那个的个数那么多的完全平方数,不难发现,这样一定能凑到,剩下的同理选一个和其它已经选的,还有自己都不能组成完全平方数的数字填充即可。
#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;
}
题意简述
一个空的二维平面上,每次加入或删除一个整点。
求每次操作完之后满足以下条件的三个点的对数。
令操作数为,保证。
保证加入点的时候平面上没有该点。
保证删除点的时候平面上有该点。
这个题由于有点难写以及比赛已经结束就没有写QWQ,所以这里口胡一下
暴力就不说了。
我们首先看统计的条件,十分特别,对于一个点,我们把它当做,那么就只统计它的左下方的点对,且满足即可。
所以我们先用离线,排序,扫描线预处理,然后用三维或者二维偏序之类的统计答案即可。
比赛Rank1后面是切了这个题的,牛客网的代码是公开的,所以想看代码的可以去牛客网看。
End
没有抽到T桖QAQ,虽然冬天,但是还是想要啊QWQ