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\)种选择。

化化式子。

\[dp[i]=\sum_{j=s-1}^{n-i}\frac{(n-i)!}{j!(n-i-j)!}*j!*(n-j-2)!*(i-1)\\ dp[i]=\sum_{j=s-1}^{n-i}\frac{(n-j-2)!}{(n-i-j)!}*(n-i)!*(i-1) \]

后面只和\(i\)有关系,前面的部分可以把分子翻转后卷积

\[f[i]=(i-2)!,g[i]=\frac{1}{(n-i)!},h=f*g\\ \sum_{j=s-1}^{n-i}\frac{(n-j-2)!}{(n-i-j)!}\\= \sum_{k=i}^{n-s+1}\frac{(k-2)!}{(k-i)!}\\ =f[i]*g[n]+f[i+1]*g[n-1]+…\\ =h[i+n] \]

也可以:

\[\sum_{j=s-1}^{n-i}\frac{(n-j-2)!}{(n-i-j)!(i-2)!}*(i-2)!\\ =\sum_{j=s-1}^{n-i}\dbinom{n-j-2}{i-2}(i-2)!\\ =\dbinom{n-j-1}{i-2}(i-2)! \]

得到\(dp[i]\)之后

\(ans[i]\)是第\(i\)个点的答案。

那么有

\[ans[i]=dp[i]-\sum_{j=i+1}^{n}\frac{ans[j]}{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

*/
posted @ 2022-04-23 18:32  lovelyred  阅读(59)  评论(0)    收藏  举报