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}),满足下列条件:
这变成了一个 (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}),第二个限制转化成:
第三个限制转化成不能有连续长为 (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]);
}