zoukankan      html  css  js  c++  java
  • AtCoder Beginner Contest 221

    G.Jumping sequence

    题目描述

    有一个平面直角坐标系,你初始在 ((0,0)),目标点是 ((x,y)),你有 (n) 步可以走,每一步步长为 (d_i),可以任意选择走上下左右,试构造方案使得能走到终点。

    (nleq 2000,d_ileq 1800)

    解法

    我们把二维平面逆时针旋转 (45) 度,那么终点就变成 ((x-y,x+y)),向右走的操作变成了 ((d,d)),向上走的操作变成了 ((-d,d)) (...) 所有的走法都可以被表示成 ((pm d,pm d))

    不难发现两维独立了,所以这从一个二维问题转化成了一个一维问题,再略微的转化一下,我们要找到一个系数数列 (px_i,py_iin{0,1}),满足下列条件:

    [sum px_icdot d_i=frac{sumd+x-y}{2},sum py_icdot d_i=frac{sumd+x+y}{2} ]

    这变成了一个 (01) 背包问题,可以用 ( t bitset) 优化到 (O(frac{n^2d}{w}))

    总结

    给高维问题降维是优化的重要方法,降维的关键是寻找维之间的独立性,这题用到的技巧是二维平面旋转 (45) 度。

    #include <cstdio>
    #include <bitset>
    #include <iostream>
    using namespace std;
    const int M = 2001;
    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[2],a[M],x,y,sum;
    bitset<3600001> dp[M];string ans;
    int Abs(int x) {return x>0?x:-x;}
    signed main()
    {
    	n=read();x=read();y=read();
    	m[0]=x-y;m[1]=x+y;
    	for(int i=0;i<n;i++) sum+=a[i]=read();
    	for(int i=0;i<2;i++)
    		if(Abs(m[i])>sum) {puts("No");return 0;}
    	for(int i=0;i<2;i++)
    	{
    		if((m[i]+sum)%2) {puts("No");return 0;}
    		else m[i]=(m[i]+sum)/2;
    	}
    	dp[0][0]=1;
    	for(int i=0;i<n;i++) dp[i+1]=dp[i]|(dp[i]<<a[i]);
    	if(!dp[n][m[0]] || !dp[n][m[1]])
    		{puts("No");return 0;}
    	for(int i=n-1;i>=0;i--)
    	{
    		int x=0;
    		for(int j=0;j<2;j++)
    			if(!dp[i][m[j]])//must decrease
    			{
    				m[j]-=a[i];
    				x+=(1<<j);
    			}
    		if(x==0) ans='L'+ans;
    		if(x==1) ans='D'+ans;
    		if(x==2) ans='U'+ans;
    		if(x==3) ans='R'+ans;
    	}
    	puts("Yes");
    	cout<<ans<<endl;
    }
    

    H.Count Multiset

    题目描述

    给定整数 (n,m),对于整数 (k=1,2...n),分别求出满足下列条件的集合个数:

    • 集合的大小为 (k)
    • 集合都是正整数且总和为 (n)
    • 一个数 (x) 的出现次数至多为 (m)

    (mleq nleq 5000)

    解法

    难以解决的限制是数 (x) 的出现次数至多为 (m),否则就是一个裸的背包问题了。

    解决方案是把集合内元素从大到小排序,然后得到差分数组 (b_i=a_i-a_{i+1}),第二个限制转化成:

    [sum_{i=1}^k b_icdot i=n ]

    第三个限制转化成不能有连续长为 (m) 的一段 (0),新增的限制是差分数组的最后一位非 (0),设 (dp[i][j]) 表示考虑差分数组的前 (i) 位总和是 (j) 且最后一位非 (0) 的方案数。

    转移可以从前 (m) 个位置而来,所以维护 (sum[j]) 记录这一段的 (dp) 值之和,就可以 (O(n^2)) 转移了。

    总结

    差分的技巧可以把某个数出现次数的限制转化成 (0) 出现次数的限制,更方便讨论。

    #include <cstdio>
    const int M = 5005;
    const int MOD = 998244353;
    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,dp[M][M],sum[M];
    signed main()
    {
    	n=read();m=read();
    	sum[0]=dp[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=i;j<=n;j++)
    			dp[i][j]=(sum[j-i]+dp[i][j-i])%MOD;
    		for(int j=0;j<=n;j++)
    		{
    			sum[j]=(sum[j]+dp[i][j])%MOD;
    			if(i>=m)
    				sum[j]=(sum[j]-dp[i-m][j]+MOD)%MOD;
    		}
    	}
    	for(int i=1;i<=n;i++)
    		printf("%d
    ",dp[i][n]);
    }
    
  • 相关阅读:
    .ssh/config 常用配置
    openresty(nginx) 配置 http与https使用同一个端口,禁止 IP 直接访问
    uni-app 入门小白纯徒手编写组件 hello-popup
    CSS 是啥?前端小白入门级理解
    爱思助手备份 iPhone 时没有设置密码,恢复备份时需要密码的问题
    introduction-to-64-bit-assembly
    flv to mp4
    Hopper Disassembler系列之Sublime Text 3 爆破
    Hopper 学习
    微信小游戏 Three.js UI 2D text 简单方案
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15367129.html
Copyright © 2011-2022 走看看