CF 783
A
题意:
给定一个\(n*m\)的网格,你要从\((1,1)\)走到\((n,m)\),连续两步的方向不能相同,问最少要走多少步?
不能到输出\(-1\)
题解:
先假设\(n<m\),特判\(n=1\)
然后\(m\)比\(n\)每多\(2\),就要靠一次上下给抵消掉,所以是\((n-1)+(m-1)+\lfloor\frac{m-n}{2}\rfloor*2\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>m;
if(n>m) swap(n,m);
if(n==1&&m>2)
{
cout<<"-1\n";
continue;
}
int t1=n-1,t2=m-1;
if(abs(t1-t2)<=1) cout<<t1+t2<<'\n';
else
{
cout<<t1+t2+(t2-t1)/2*2<<'\n';
}
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
B
题意:
给\(n\)个人和\(m\)把椅子,\(n\)个人坐成一圈,第\(i\)个人左右两边至少空着\(a[i]\)个人,问能否安排下。
题解:
先按\(a[i]\)排序,那么\(a\)最大的人旁边肯定坐着\(a\)第二大的人,这样就可以抵消掉一个较大的\(a\)。
如此类推,每个人的\(a\)都可以抵消掉一个。
设\(sum=a[1]+a[2]+…+a[n]\)
但是最后一个人要去挨着\(a[n]\),所以至少需要\(sum-a[1]+a[n]\)把椅子。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m,sum;
int a[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>m;sum=0;
for(int i=1;i<=n;++i)
{
cin>>a[i];
sum+=a[i];
}
sort(a+1,a+n+1);
sum+=n+a[n]-a[1];
if(sum<=m) cout<<"YES\n";
else cout<<"NO\n";
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
C
题意:
给定数组\(A\),你有空数组\(B\),每次可以选定一个位置\(i\),让\(b[i]+=a[i]\)或让\(b[i]-=a[i]\),问最少多少次能把\(B\)数组变成严格上升的?
\(n\leq5000,1\leq a[i]\leq 10^9\)
题解:
数组中一定存在某个位置,最终\(b[i]=0\),不然就可以让他不上升,后面的数或多或少下降一点,让答案更优秀。
枚举这个位置,那么其他位置的数字其实是一定的,在他前面的就尽可能比后一个数只小一点,在他后面的尽可能比前一个数只大一点。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int a[N];
int b[N],c[N],pre[N],suf[N];
int ans=inf*inf;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
for(int i=1;i<=n;++i)
{
int ret=0,sum=0;
for(int j=i-1;j>=1;--j)
{
int tmp=sum/a[j];
if(tmp*a[j]<=sum) ++tmp;
ret+=tmp;
sum=tmp*a[j];
//cout<<ret<<' '<<sum<<"!!"<<endl;
}
sum=0;
for(int j=i+1;j<=n;++j)
{
int tmp=sum/a[j];
if(tmp*a[j]<=sum) ++tmp;
ret+=tmp;
sum=tmp*a[j];
//cout<<ret<<' '<<sum<<"!!"<<endl;
}
// cout<<"----"<<endl;
ans=min(ans,ret);
}
cout<<ans<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
D
题意:
给定数组\(A\),要求把这个\(A\)划分成若干个非空子段。
对于某个子段\([l,r]\)来说,设\(s=\sum_{i=l}^ra[i]\):
若\(s>0\),则\(val_{l,r}=(r-l+1)\)
若\(s==0\),则\(val_{l,r}=0\)
若\(s<0\),则\(val_{l,r}=-(r-l+1)\)
求价值最大的划分方案的价值。
\(n\leq 5*10^5,|a_i|\leq 10^9\)
题解:
设\(s[i]=\sum_{i=1}^{i}a[i]\)
我们先把前缀和离散化,然后设\(dp[i]\)是\(1\sim i\)的划分的最大价值。
对于某个位置\(j<i\),如果\(s[j]<s[i]\),那么说明这段的贡献是\(dp[j]+(i-j)\)
我们不妨把\(dp[j]-j\)存到某个数据结构里,然后每次从这个数据结构里找到最大的数字,并\(+i\),赋值给\(dp[i]\)。
这是对于\(s[j]<s[i]\)的情况,\(s[j]>s[i]\)的情况只要取反(存\(dp[j]+j\),取最大值\(-i\))。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=5e5+10,mod=998244353,inf=2e9;
int n,m;
int a[N],s[N],c[N],num;
int dp[N];
struct segment_tree
{
int ans[N<<2][3];
inline void build(int l,int r,int p)
{
ans[p][0]=ans[p][1]=ans[p][2]=-inf;
if(l==r) return;
build(l,mid,ls(p));
build(mid+1,r,rs(p));
}
inline void update(int pos,int l,int r,int p,int k,int id)
{
if(l==r)
{
ans[p][id]=max(ans[p][id],k);
return;
}
if(pos<=mid) update(pos,l,mid,ls(p),k,id);
if(pos>mid) update(pos,mid+1,r,rs(p),k,id);
ans[p][id]=max(ans[ls(p)][id],ans[rs(p)][id]);
}
inline int query(int tl,int tr,int l,int r,int p,int id)
{
if(tl<=l&&r<=tr) return ans[p][id];
int ret=-inf;
if(tl<=mid) ret=max(ret,query(tl,tr,l,mid,ls(p),id));
if(tr>mid) ret=max(ret,query(tl,tr,mid+1,r,rs(p),id));
return ret;
}
}T;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int TT;cin>>TT;
while(TT--)
{
cin>>n;
num=0;
c[++num]=0;
for(int i=1;i<=n;++i)
{
dp[i]=-inf;
cin>>a[i];
s[i]=s[i-1]+a[i];
c[++num]=s[i];
}
sort(c+1,c+num+1);
num=unique(c+1,c+num+1)-c-1;
T.build(1,num,1);
int tmp=lower_bound(c+1,c+num+1,0)-c;
T.update(tmp,1,num,1,0,0);
T.update(tmp,1,num,1,0,1);
T.update(tmp,1,num,1,0,2);
for(int i=1;i<=n;++i)
{
int tmp=lower_bound(c+1,c+num+1,s[i])-c;
if(tmp>1) dp[i]=max(dp[i],T.query(1,tmp-1,1,num,1,0)+i);
dp[i]=max(dp[i],T.query(tmp,tmp,1,num,1,1));
if(tmp<num) dp[i]=max(dp[i],T.query(tmp+1,num,1,num,1,2)-i);
T.update(tmp,1,num,1,dp[i]-i,0);
T.update(tmp,1,num,1,dp[i],1);
T.update(tmp,1,num,1,dp[i]+i,2);
}
cout<<dp[n]<<'\n';
//(j,s[j],dp[j])
//dp[i]=dp[j]+(i-j)[s[i]-s[j]>0]
}
}
}
signed main()
{
red::main();
return 0;
}
/*
10
3
1 2 -3
4
0 -2 3 -4
5
-1 -2 3 -1 -1
6
-1 2 -3 4 -5 6
7
1 -1 -1 1 -1 -1 1
3
1 2 -3
4
0 -2 3 -4
5
-1 -2 3 -1 -1
6
-1 2 -3 4 -5 6
7
1 -1 -1 1 -1 -1 1
*/
E
题意:
有\(n*n\)的网格,有一些皇后,她可以攻击同一行,同一列和她所在的主对角线上的所有位置,问最少多少个皇后才能攻击到棋盘上所有位置,并输出构造方案。
\(n\leq 10^5\)
题解:
特判掉\(n\leq 2\)
爹教的方法:
先求个数,假设我们有\(x\)个皇后,它们可以攻击到\(x\)行和\(x\)列,那么将剩下\((n-x)*(n-x)\)个格子没有被攻击到,假设这些格子都在右下角,那么至少占据了\(2*(n-x)-1\)条不同的对角线。
所以我们要找最小的\(x\),满足\(x\geq 2*(n-x)-1\)
如何构造方案数呢?
对于\(n=3*k+2\)的情况,\(x=2*k+1\),我们可以在左上角沿着反对角线放\(k+1\)个棋子,在右下角沿着反对角线放\(k\)个棋子。
比如\(n=8,k=5\):
左上角\((1,3),(2,2),(3,1)\)放三个。
右下角\((7,8),(8,7)\)放两个。
对于\(n=3*k+1\)或\(n=3*k+0\),可以在右下角放到\(n=3*k+2\)为止。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n;
struct node
{
int x,y;
}st[N];
bool vis[N];
int top;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
if(n<=2)
{
cout<<1<<'\n';
cout<<"1 1"<<'\n';
return;
}
int x=1;
for(;;++x)
{
if(x>=2*(n-x)-1) break;
}
cout<<x<<'\n';
while(n%3!=2)
{
cout<<n<<' '<<n<<'\n';
--n;
--x;
}
int a=(n+1)/3;
for(int i=1;i<=a;++i)
{
cout<<i<<' '<<a-i+1<<'\n';
}
for(int i=1;i<a;++i)
{
cout<<n-a+i+1<<' '<<n-i+1<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
F
题意:
给定一棵树,有\(n\)个节点,如果两条边共用一个节点,则说这两条边相邻。
如果一条边有偶数条边和它相邻,则可以删去这条边,问能不能把所有的边都删掉,构造方案。
\(n\leq 10^5\)
题解:
官方题解真是勾八。
我们给边黑白染色,如果一个点度数是偶数,则应该有数量相等的白色边和黑色边,否则白色边应该比黑色边多一条。(不满足这两个条件就是无解)。
这是什么意思呢?我们可以认为连向叶子的边都是白色边,对某个节点开始删除它周围的边的时候,应该先删除白色的边。
如果一个节点连了超过两个叶子(白色边比黑色边多\(2\)),那么这些叶子不可能安然无恙地删掉。
如果一个节点下面白色边比黑色边多,就把它的父亲边设置为,否则把这条边设置为白色,接下来只要去轮流删白色边和黑色边。
我们从根节点开始,但是对一条边来说,如果不是父亲边,不应该直接删掉,而应该递归进连向的节点。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int b[N];
vector<int> eg[N];
bool flag;
inline void dfs(int now,int fa)
{
int cnt[2]={0,0};
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
++cnt[b[t]];
}
if(now!=1)
{
b[now]=(cnt[0]>=cnt[1]);
++cnt[b[now]];
}
if(cnt[1]-cnt[0]<0||cnt[1]-cnt[0]>1) flag=1;
//cout<<now<<' '<<b[now]<<' '<<cnt[0]<<' '<<cnt[1]<<"------------"<<endl;
}
inline void solve(int now,int fa)
{
vector<int> p[2];
for(int t:eg[now])
{
if(t==fa) p[b[now]].emplace_back(now);
else p[b[t]].emplace_back(t);
}
int s=eg[now].size(),opt=s%2;
for(int i=0;i<s;++i)
{
int t=p[opt].back();
if(t==now) cout<<now<<' '<<fa<<'\n';
else solve(t,now);
p[opt].pop_back();
opt^=1;
}
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;flag=0;
for(int i=1;i<=n;++i)
{
eg[i].clear();
b[i]=0;
}
for(int i=1;i<n;++i)
{
int x,y;cin>>x>>y;
eg[x].emplace_back(y);
eg[y].emplace_back(x);
}
dfs(1,0);
if(flag)
{
cout<<"NO\n";
continue;
}
cout<<"YES\n";
solve(1,0);
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
G
题意:
\(n\)个节点的一棵树,每个点的父亲节点的编号都要小于自己,问以每个点为重心的树有多少种。
题解:
设\(s=\lfloor \frac{n+1}{2}\rfloor\)
设\(dp[i]\)是\(i\)的子树(包括\(i\))至少有\(s\)个节点的方案数。
\(dp[1]=(n-1)!,dp[k]=0,(k>s)\)
最后的答案一定是\(dp[i]\)的一部分,否则\(i\)的父亲方向的边就有超过\(s\)个节点了。
怎么得到捏,我们钦定\(j\)个点是\(i\)的子树,剩下的点不是\(i\)的子树。
\(dp[i]=\sum_{j=s-1}^{n-i}\dbinom{n-i}{j}*j!*(n-j-2)!*(i-1)\)
组合数是选\(j\)个点出来,这\(j\)个点之间可以任意相连,有一个\(j!\),剩下的\(n-j-2\)个点之间(\(j\)个点去掉\(1\)和\(i\))可以随意相连。
\(i\)号节点的父亲有\(i-1\)种选择。
化化式子。
后面只和\(i\)有关系,前面的部分可以把分子翻转后卷积
也可以:
得到\(dp[i]\)之后
设\(ans[i]\)是第\(i\)个点的答案。
那么有
因为在\(dp[i]\)中,重心要么是\(i\),要么是\(i\)的某个子节点\(j\),而子节点\(j\)的父亲是\(i\)的概率是\(\frac{1}{i}\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int fac[N],inv[N];
int dp[N],suf[N],ret[N];
inline int fast(int x,int k)
{
int ret=1;
while(k)
{
if(k&1) ret=ret*x%mod;
x=x*x%mod;
k>>=1;
}
return ret;
}
inline int C(int n,int m)
{
if(n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;m=(n+1)/2;
fac[0]=inv[0]=1;
for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
inv[n]=fast(fac[n],mod-2);
for(int i=n-1;i>=1;--i) inv[i]=inv[i+1]*(i+1)%mod;
dp[1]=fac[n-1];
for(int i=2;i<=m;++i)
{
dp[i]=C(n-m,i-1)*fac[i-2]%mod*fac[n-i]%mod*(i-1)%mod;
}
for(int i=m;i>=1;--i)
{
ret[i]=(dp[i]-suf[i+1]*fast(i,mod-2)%mod+mod)%mod;
suf[i]=(suf[i+1]+ret[i])%mod;
}
for(int i=1;i<=n;++i) cout<<ret[i]<<" \n"[i==n];
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
卷积版
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
//#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int fac[N],inv[N];
int dp[N],suf[N],ret[N];
inline int fast(int x,int k)
{
int ret=1;
while(k)
{
if(k&1) ret=ret*x%mod;
x=x*x%mod;
k>>=1;
}
return ret;
}
inline int C(int n,int m)
{
if(n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
namespace NTT
{
const int N=1e6+10,g=3,gi=332748118,mod=998244353;
int pos[N];
int limit=1,len;
inline void ntt(vector<int> &a,int inv)
{
//for(int i=0;i<limit;++i) cout<<a[i]<<" \n"[i==limit-1];
for(int i=0;i<limit;++i)
if(i<pos[i]) swap(a[i],a[pos[i]]);
for(int mid=1;mid<limit;mid<<=1)
{
int Wn=fast(inv?g:gi,(mod-1)/(mid<<1));
for(int r=mid<<1,j=0;j<limit;j+=r)
{
int w=1;
for(int k=0;k<mid;++k,w=w*Wn%mod)
{
int x=a[j+k],y=w*a[j+k+mid]%mod;
a[j+k]=x+y;
if(a[j+k]>=mod) a[j+k]-=mod;
a[j+k+mid]=x-y;
if(a[j+k+mid]<0) a[j+k+mid]+=mod;
}
}
}
if(inv) return;
inv=fast(limit,mod-2);
for(int i=0;i<limit;++i) a[i]=a[i]*inv%mod;
}
inline vector<int> mul(vector<int> a,vector<int> b)
{
int n=a.size(),m=b.size();
limit=1,len=0;
while(limit<n+m) limit<<=1,++len;
for(int i=0;i<limit;++i)
pos[i]=(pos[i>>1]>>1)|((i&1)<<(len-1));
a.resize(limit,0),b.resize(limit,0);
ntt(a,1);ntt(b,1);
//for(int i=0;i<n;++i) cout<<a[i]<<' '<<b[i]<<'\n';
for(int i=0;i<limit;++i) a[i]=a[i]*b[i]%mod;
ntt(a,0);
//for(int i=0;i<limit;++i) cout<<a[i]<<"!!"<<endl;
//for(int i=n+m-1;i<limit;++i) a[i]=0;
return a;
}
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;m=(n+1)/2;
fac[0]=inv[0]=1;
for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
inv[n]=fast(fac[n],mod-2);
for(int i=n-1;i>=1;--i) inv[i]=inv[i+1]*(i+1)%mod;
dp[1]=fac[n-1];
vector<int> a,b,c;
a.resize(n+1,0),b.resize(n+1,0);
for(int i=0;i<=n;++i) b[i]=inv[n-i];
for(int i=2;i<=m;++i) a[i]=fac[i-2];
//for(int i=0;i<=n;++i) cout<<b[i]<<"!!!"<<endl;
//reverse(b.begin(),b.end());
c=NTT::mul(a,b);
//for(int i=0;i<n;++i) cout<<c[i]<<"!!"<<endl;
dp[1]=fac[n-1];
for(int i=2;i<=m;++i)
{
dp[i]=c[n+i]*fac[n-i]%mod*(i-1)%mod;
}
for(int i=m;i>=1;--i)
{
ret[i]=(dp[i]-suf[i+1]*fast(i,mod-2)%mod+mod)%mod;
suf[i]=(suf[i+1]+ret[i])%mod;
}
for(int i=1;i<=n;++i) cout<<ret[i]<<" \n"[i==n];
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/