zoukankan      html  css  js  c++  java
  • NOMURA Programming Contest 2021

    C.Odd Even Sort

    题目描述

    点此看题

    有一个长度为 (n) 的排列,要求通过 (n^2) 次操作把他变成 (1,2...n) 的排列:

    • 奇数次操作可以操作奇数位置 (x),即交换 (p[x],p[x+1])
    • 偶数次操作可以操作偶数位置 (x),即交换 (p[x],p[x+1])

    (nleq 500)

    解法

    (n^2) 次操作这么宽松,可以考虑一下一次把 (1,2,3...) 换到第一个位置,第二个位置,第三个位置 (...)

    看上去交换是很容易的,如果目标数字在奇数位置,现在操作也在奇数位置怎么办?这就涉及了浪费步数的问题,递归到 (n=3) 的情况是可以特判的,那么我们考虑 (n>3) 的情况。

    可以直接操作目标数字,再操作初始目标数字位置的前一位浪费步数,那么现在奇偶性就对得上了,直接往前一直操作就行。注意如果目标数字在 (n) 这个位置,就操作 (n-2) 浪费步数即可。

    对于 (n=3) 一直无脑乱换,可以证明有限步内达到答案状态。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 505;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,nw,f,ji,ou,p[M];vector<int> ans;
    void work(int &nw)
    {
        if(nw) swap(p[ji],p[ji+1]),ans.push_back(ji);
        else swap(p[ou],p[ou+1]),ans.push_back(ou);
        nw^=1;
    }
    void solve(int x)
    {
        if(x==n-2)
        {
            while(!(p[n-2]<p[n-1] && p[n-1]<p[n]))
            {
                if(nw) swap(p[ji],p[ji+1]),ans.push_back(ji);
                else swap(p[ou],p[ou+1]),ans.push_back(ou);
                nw^=1;
            }
            return ;
        }
        for(int i=x;i<=n;i++)
            if(p[i]==x)
            {
            	if(i==x) {solve(x+1);return ;}
                if(i%2==nw)
                {
                	if(i==n)
                	{
                		swap(p[n-2],p[n-1]),ans.push_back(n-2);
                		nw^=1;
    				}
    				else
    				{
    					swap(p[i],p[i+1]),ans.push_back(i);
    	            	swap(p[i-1],p[i]),ans.push_back(i-1);
    	            	i++;
    				}
                }
                for(int j=i-1;j>=x;j--)
                {
                    swap(p[j],p[j+1]);
                    ans.push_back(j);nw^=1;
                }
                solve(x+1);
                return ;
            }
    }
    signed main()
    {
        T=read();
        while(T--)
        {
            n=read();ans.clear();nw=1;
            if(n%2==0) ji=n-1,ou=n-2;
            else ji=n-2,ou=n-1;
            for(int i=1;i<=n;i++)
                p[i]=read();
            if(n==2)
            {
                if(p[1]==1) puts("0
    ");
                else printf("1
    1
    ");
                continue;
            }
            solve(1);
            printf("%d
    ",ans.size());
            for(int i=0;i<ans.size();i++)
                printf("%d ",ans[i]);
            puts("");
        }
    }
    
    

    D.1 or 2

    题目描述

    点此看题

    (n) 个物品,每次拿一个或者拿两个,记录下拿走物品的权值和 (x),最小化 (max(x)-min(x))

    (1leq nleq 5000)

    解法

    不难猜到一个贪心就是我们把最大的和最小的配对,但这是可以证明的:

    考虑 (Aleq Bleq Cleq D) 两种配对方式满足:

    (max(A+C,B+D)geq max(A+D,B+C),min(A+C,B+D)leqmin(A+D,B+C))

    显然第二种配对方法最优,所以贪心成立。

    然后我来了发贪心交上去 ( t Wa) 穿了,哎,就是我的思维不严谨,我是傻逼。

    注意这种贪心的适用范围是只存在两两配对的情况,如果存在单个配对就不行了。但是我们可以加入 (0),让单个配对的东西和 (0) 配对即可,所以我们要枚举加入 (0) 的个数,时间复杂度 (O(n^2))

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int inf = 2e9;
    const int M = 10005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans,a[M];
    signed main()
    {
    	n=m=read();ans=inf;
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	for(int l=0;l<=n;l++)
    	{
    		sort(a+1,a+1+m);
    		if(m%2==0)
    		{
    			int mx=-inf,mi=inf;
    			for(int i=1;i<=m/2;i++)
    			{
    				mx=max(mx,a[i]+a[m-i+1]);
    				mi=min(mi,a[i]+a[m-i+1]);
    			}
    			ans=min(ans,mx-mi);
    		}
    		a[++m]=0;
    	}
    	printf("%d
    ",ans);
    }
    

    E.Directed Tree

    题目描述

    给定 (n) 个点的树,求排列 (a) 满足 (a_i) 不是 (i) 的祖先(如果 (i=a_i) 仍然认为合法)

    (1leq nleq 2000)

    解法

    这道题一看就很容斥了,我们枚举 (a_x)(x) 祖先的位置数量 (i),那么有:

    [ans=sum_{i=0}^n(-1)^icdot f[i]cdot (n-i)! ]

    其中 (f[i]) 表示钦定 (i) 个位置不合法的方案数,可以用树形 (dp) 算一下,设 (dp[i][j]) 表示以 (i) 为根的子树中钦定了 (j) 个位置不合法的方案数,每次我们考虑让 (u) 的某个儿子 (v)(a_v=u) 而不是让 (a_u) 成为 (u) 的某个祖先,这样只管子树内转移才不会有冲突,然后弄个树上背包合并一下子树不合法位置个数即可。

    时间复杂度 (O(n^2))

    #include <cstdio>
    const int M = 2005;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,tot,ans,f[M],siz[M],tmp[M],fac[M],dp[M][M];
    struct edge
    {
    	int v,next;
    }e[2*M];
    void dfs(int u,int fa)
    {
    	dp[u][0]=1;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa) continue;
    		dfs(v,u);
    		for(int i=siz[u];i>=0;i--)
    			for(int j=siz[v];j>=0;j--)
    				tmp[i+j]=(tmp[i+j]+dp[u][i]*dp[v][j])%MOD;
    		siz[u]+=siz[v];
    		for(int i=0;i<=siz[u];i++)
    			dp[u][i]=tmp[i],tmp[i]=0;
    	}
    	for(int i=siz[u];i>=0;i--)
    		dp[u][i+1]=(dp[u][i+1]+dp[u][i]*(siz[u]-i))%MOD;
    	siz[u]++;
    }
    signed main()
    {
    	n=read();
    	for(int i=2;i<=n;i++)
    	{
    		int j=read();
    		e[++tot]=edge{j,f[i]},f[i]=tot;
    		e[++tot]=edge{i,f[j]},f[j]=tot;
    	}
    	dfs(1,0);fac[0]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=0;i<=n;i++)
    	{
    		if(i&1) ans=(ans-dp[1][i]*fac[n-i])%MOD;
    		else ans=(ans+dp[1][i]*fac[n-i])%MOD;
    	}
    	printf("%lld
    ",(ans+MOD)%MOD);
    }
    

    F.Logical Operations on Tree

    题目描述

    点此看题

    给定一棵 (n) 个节点的树,每个节点会被标记 (0/1),每条边会被标记为 AND/OR

    进行任意 (n-1) 次操作,每次操作选择有边相连的两个点合并成一个新的点,新点的标记为原来点标记经过边对应位运算的结果。

    求最后整棵树能只剩下标记 (1) 为的点的树的数量。

    (nleq 10^5)

    解法

    思维方式真的很重要,这道题是树上删除问题,从叶子角度考虑最简单,我们讨论叶子和对应边的情况:

    • 叶子标记为 (1),边为 or,那么叶子以外的部分任意操作,最后都能合法。
    • 叶子标记为 (1),边为 and,那么这个点一定无影响。
    • 叶子标记为 (0),边为 or,那么这个点一定无影响。
    • 叶子标记为 (0),边为 and,那么操作这个点会让父亲为 (0),优先操作一定最优。

    综上,除了第一种情况,我们其它都可以无脑操作叶子。明确上面的结论之后我们可以暴力讨论 (dp),但是官方给出更方便的做法。设 (f[i]) 表示子树内不存在 1 or 的情况,(g[i]) 表示 (f[i]) 的这些情况中合法情况个数,转移:

    • 考虑边是 and/or,除掉 1 or 的情况:(f[u]leftarrow(2f[v]-g[i])cdot f[u])
    • 考虑是 1 and 或者 0 or(g[u]leftarrow g[u]cdot f[u])

    最后的答案是 (2^{2n-1}-f[1]+g[1]),这是一个简单的容斥。

    #include <cstdio>
    const int M = 100005;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,tot,f[M],F[M],G[M];
    struct edge
    {
    	int v,next;
    }e[2*M];
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void dfs(int u,int fa)
    {
    	F[u]=2;G[u]=1;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa) continue;
    		dfs(v,u);
    		F[u]=(F[v]*2-G[v])*F[u]%MOD;
    		G[u]=G[u]*F[v]%MOD; 
    	}
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		e[++tot]=edge{v,f[u]},f[u]=tot;
    		e[++tot]=edge{u,f[v]},f[v]=tot;
    	}
    	dfs(1,0);
    	printf("%lld
    ",(qkpow(2,2*n-1)-F[1]+G[1]+MOD)%MOD);
    }
    
  • 相关阅读:
    Entity Framework 连接低版本数据库
    Validate Disk Failover Failed
    Unable to create Azure Mobile Service: Error 500
    查看Visual Studio异常内容
    RDLC An unexpected error occurred while compiling expressions. Native compiler return value: '-1073741511'
    Redis 64 steps
    IQueryable join 的问题
    jquery 通知页面变化
    jquery 让滚动条处于div底部
    SSIS 文件系统任务无法使用变量配置目标路径
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14843171.html
Copyright © 2011-2022 走看看