AtCoder Beginner Contest 221
G.Jumping sequence
题目描述
有一个平面直角坐标系,你初始在 \((0,0)\),目标点是 \((x,y)\),你有 \(n\) 步可以走,每一步步长为 \(d_i\),可以任意选择走上下左右,试构造方案使得能走到终点。
\(n\leq 2000,d_i\leq 1800\)
解法
我们把二维平面逆时针旋转 \(45\) 度,那么终点就变成 \((x-y,x+y)\),向右走的操作变成了 \((d,d)\),向上走的操作变成了 \((-d,d)\) \(...\) 所有的走法都可以被表示成 \((\pm d,\pm d)\)
不难发现两维独立了,所以这从一个二维问题转化成了一个一维问题,再略微的转化一下,我们要找到一个系数数列 \(px_i,py_i\in\{0,1\}\),满足下列条件:
这变成了一个 \(01\) 背包问题,可以用 \(\tt 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\)
\(m\leq n\leq 5000\)
解法
难以解决的限制是数 \(x\) 的出现次数至多为 \(m\),否则就是一个裸的背包问题了。
解决方案是把集合内元素从大到小排序,然后得到差分数组 \(b_i=a_i-a_{i+1}\),第二个限制转化成:
第三个限制转化成不能有连续长为 \(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\n",dp[i][n]);
}