无关前置
最近同学都在打牛客网的比赛并且博主也在写一下牛客网的题,博主就去看了看,打了一场,题目质量还是非常不错的。我才不会告诉你我没开long long错了好久QWQ
题意简述
给出长度为 n n n的序列 a a a, 求有多少对数对 ( i , j ) ( 1 ≤ i < j ≤ n ) (i, j) (1 \leq i < j \leq n) (i,j)(1≤i<j≤n) 满足 a i + a j a_i + a_j ai+aj 为完全平方数。
数据范围: 1 ≤ n , a ≤ 1 0 5 1\leq n,a\leq10^5 1≤n,a≤105
这个签到题吧,范围十分的小,值域才 1 0 5 10^5 105,开个数组记录一下每个数字出现次数,然后 n \sqrt{n} n的每次枚举平方就好啦,复杂度 O ( n n ) O(n\sqrt{n}) O(nn)
#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≤100000,m≤200000( n n n为点数, m 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 n个点的树,求有多少种删边方案,使得删后的图每个连通块大小小于等于 k k k,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对 998244353 998244353 998244353取模
数据范围: 2 ≤ n , k ≤ 2000 2\leq n,k\leq 2000 2≤n,k≤2000
这个就是个裸的树上背包问题,我们定义状态 f [ i ] [ j ] f[i][j] f[i][j],表示以 i i i为根的子树,连通块大小为 j j j的方案数,其中我们特殊规定当 j = 0 j=0 j=0时,表示 i i i点不与上方父亲结点连通的方案数,所以就深搜一遍,树形 D P \rm DP DP,复杂度看似最坏为 n 3 n^3 n3,实际上由于每次枚举不是枚举所有的点数,所以复杂度大概算下来为 n 2 ∼ n 5 3 n^2\sim n^\frac{5}{3} n2∼n35左右,是不会达到 n 3 n^3 n3的,所以完全能够(具体的一些证明可以去网上找一些树上背包的题的题解,里面可能有)。
所以直接打就好了,不要害怕超时。
#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 n次操作,每次操作给出 x , k , p x,k,p x,k,p,执行以下操作:
- 在 S S S中加入 x x x
- 输出 ∑ y ∈ S g c d ( x , y ) k ( m o d p ) \sum\limits_{y\in S}gcd(x,y)^k(\rm mod\ p) y∈S∑gcd(x,y)k(mod p)
1 ≤ n , x , k , p ≤ 1 0 5 1\leq n,x,k,p\leq 10^5 1≤n,x,k,p≤105
显然暴力就为
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)
但是我们这里考虑,
g
c
d
gcd
gcd为最大公约数,所以一定为给出数字的因子,而在
1
0
5
10^5
105内的一个数字的因子数量不会很多(可以自己用线性筛筛一遍看看),所以我们考虑将每个插入的数拆成因子插入,然后我们从大到小枚举它的因子,因为对于一个它的因子,如果当前枚举的肯定是较大的,当这个因子有的时候,那么肯定会成为答案,记这个因子为
a
a
a,出现个数为
c
c
c,那么贡献为
c
×
(
a
k
)
c\times (a^k)
c×(ak),然后统计完这个我们需要把这个因子的因子的个数全部减去
c
c
c,因为包含该因子的数字我们已经算了,不能重复算,而后面只会枚举比它小的数,所以只用减去它的因子的数。
我们每次 n \sqrt{n} n的统计因子,然后记最多因子个数为 w w w,每次 w 2 w^2 w2的统计答案,然后加一点常数优化和剪枝,就可以过了,复杂度 O ( n n × w 2 ) O(n\sqrt{n}\times w^2) O(nn×w2),但是注意这里的 w 2 w^2 w2是远远达不到的。
#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,k,求一个长度为 n n n 的数组 a a a, 满足有恰好 k k k 对数对 ( i , j ) ( 1 ≤ i < j ≤ n ) (i, j) (1 \leq i < j\leq n) (i,j)(1≤i<j≤n)满足 a i + a j a_i + a_j ai+aj为完全平方数。如果不存在,输出 − 1 -1 −1 。
数据范围: n ≤ 1 0 5 , k ≤ 1 0 10 n\leq 10^5,k\leq 10^{10} n≤105,k≤1010
这个题非常的良心啊,第一题的标程就是它的检验器啊。
我们首先分析,对于无解的情况,当你无论如何都凑不到 k k k个时就无解,所以对于一个长度为 n n n的序列,它最多能组合出 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n−1)个完全平方数,所以当 k > n × ( n − 1 ) 2 k>\frac{n\times(n-1)}{2} k>2n×(n−1)我们就可以判断它无解。
然后对于有解的情况,由于只让输出一种方案,所以我们特殊构造即可,首先我们找几个数字 x x x, x x x要满足 2 x 2x 2x为完全平方数,然后因为数字可以重复,所以如果我们放了 i i i个 x x x,那么它就会有 i × ( i − 1 ) 2 \frac{i\times(i-1)}{2} 2i×(i−1)的贡献。
所以我们分类讨论,对于 k k k可以写成 i × ( i − 1 ) 2 \frac{i\times(i-1)}{2} 2i×(i−1)的形式的情况,我们直接输出 i i i个 x x x,然后找一个 y y y,满足 2 y 2y 2y和 x + y x+y x+y都不为完全平方数,剩下的 n − i n-i n−i个数字由这个数字填充即可。
然后对于 k k k不能写成 i × ( i − 1 ) 2 \frac{i\times(i-1)}{2} 2i×(i−1)的形式的情况,我们可以多选几种 x x x,然后先凑成近似 k k k,剩下的我们找一个 w w w,满足 w + 其中一个 x w+\text{其中一个}x w+其中一个x为完全平方数,而 2 w 2w 2w不为完全平方数,那么它就可以贡献出选的那个 x x x的个数那么多的完全平方数,不难发现,这样一定能凑到 k k k,剩下的同理选一个和其它已经选的 x 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
p1,p2,p3的对数。
- p 1 . y > p 2 . y > p 3 . y p_1.y>p_2.y>p_3.y p1.y>p2.y>p3.y
- p 1 . x > m a x ( p 2 . x , p 3 . x ) p_1.x>max(p_2.x,p_3.x) p1.x>max(p2.x,p3.x)
令操作数为
n
n
n,保证
n
≤
60000
,
1
≤
x
,
y
≤
n
n\leq 60000,1\leq x,y\leq n
n≤60000,1≤x,y≤n。
保证加入点的时候平面上没有该点。
保证删除点的时候平面上有该点。
这个题由于有点难写以及比赛已经结束就没有写QWQ,所以这里口胡一下
暴力就不说了。
我们首先看统计的条件,十分特别,对于一个点,我们把它当做 p 1 p_1 p1,那么就只统计它的左下方的点对,且满足 p 2 . y > p 3 . y p_2.y>p_3.y p2.y>p3.y即可。
所以我们先用离线,排序,扫描线预处理,然后用三维或者二维偏序之类的统计答案即可。
比赛Rank1后面是切了这个题的,牛客网的代码是公开的,所以想看代码的可以去牛客网看。
End
没有抽到T桖QAQ,虽然冬天,但是还是想要啊QWQ