【CF1290F】Making Shapes(数位DP)

256 篇原创祭

题面

给定 n n n 个向量。你需要按照如下方式画图:

  • 初始点在 ( 0 , 0 ) (0,0) (0,0)
  • 选择一个向量,从当前点连向 当前点加上该向量得到的点
  • 从父第二个操作,最后回到 ( 0 , 0 ) (0,0) (0,0) 并停止。

这样你可以得到一个多边形,问可以画出来多少种平移不重复的多边形,使其可以放进 m × m m\times m m×m 的矩形里。输出答案对 998244353 998244353 998244353 取模。

n ≤ 5 , m ≤ 1 0 9 , ∣ x i ∣ , ∣ y i ∣ ≤ 4 n\leq 5,m\leq 10^9,|x_i|,|y_i|\leq 4 n5,m109,xi,yi4

题解

这次是真不给思路了,我只能说出题人🐂🍺。

数位DP(二进制位),设 d p [ i ] [ l 1 ] [ r 1 ] [ l 2 ] [ r 2 ] [ u 1 ] [ u 2 ] dp[i][l_1][r_1][l_2][r_2][u_1][u_2] dp[i][l1][r1][l2][r2][u1][u2] 表示满足下列条件的向量组合的方案数:

  • 考虑到从小到大第 i + 1 i+1 i+1 位。
  • 横坐标正数之和右移 i i i 后等于 l 1 l_1 l1
  • 横坐标负数之和绝对值右移 i i i 后等于 r 1 r_1 r1
  • 纵坐标正数之和右移 i i i 后等于 l 2 l_2 l2
  • 纵坐标负数之和绝对值右移 i i i 后等于 r 2 r_2 r2
  • 当前已考虑的位数中,是1否0横坐标正数之和大于 m( u 1 u_1 u1)。
  • 当前已考虑的位数中,是1否0纵坐标正数之和大于 m( u 2 u_2 u2)。

转移时枚举 2 n 2^n 2n 种情况,决定每个向量的个数的第 i + 1 i+1 i+1为 0 或 1 ,然后按照定义推出目标状态的 l 1 , r 1 , l 2 , r 2 , u 1 , u 2 l_1,r_1,l_2,r_2,u_1,u_2 l1,r1,l2,r2,u1,u2 ,转移。最后,边界状态 d p [ log ⁡ m + 1 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] = 1 dp[\log m+1][0][0][0][0][0][0]=1 dp[logm+1][0][0][0][0][0][0]=1 。答案减去无意义的“原地画点”,为 d p [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] − 1 dp[0][0][0][0][0][0][0]-1 dp[0][0][0][0][0][0][0]1

数据极小,能跑过。

CODE

为了更好地理解,可以看代码。我写了很好理解的记忆化搜索。

//真的猛士,敢于将打到一半的代码提交
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 998244353;
int n,m,s,o,k;
struct it{
	int x,y;
	it(){x=y=0;}
	it(int X,int Y){x=X;y=Y;}
}a[10];
int dp[35][20][20][20][20][2][2];
bool f[35][20][20][20][20][2][2];
int DP(int x,int l1,int r1,int l2,int r2,int u1,int u2) {
	if(x > 31) return l1+r1+l2+r2+u1+u2 == 0;
	int &F = dp[x][l1][r1][l2][r2][u1][u2];
	if(f[x][l1][r1][l2][r2][u1][u2]) return F;
	f[x][l1][r1][l2][r2][u1][u2] = 1; F = 0;
	for(int i = 0;i < (1<<n);i ++) {
		int _l1 = l1,_r1 = r1,_l2 = l2,_r2 = r2;
		for(int j = 0;j < n;j ++) {
			if(!(i & (1<<j))) continue;
			if(a[j].x > 0) _l1 += a[j].x; else _r1 -= a[j].x;
			if(a[j].y > 0) _l2 += a[j].y; else _r2 -= a[j].y;
		}
		if(((_l1^_r1)&1) || ((_l2^_r2)&1)) continue;
		int dx = _l1&1,dy = _l2&1,v1 = u1,v2 = u2,dm = (m&(1<<x)) ? 1:0;
		if(dx > dm) v1 = 1; else if(dx < dm) v1 = 0;
		if(dy > dm) v2 = 1; else if(dy < dm) v2 = 0;
		(F += DP(x+1,_l1>>1,_r1>>1,_l2>>1,_r2>>1,v1,v2)) %= MOD;
	}
//	printf("dp[%d][%d,%d,%d,%d][%d%d] = %d\n",x,l1,r1,l2,r2,u1,u2,F);
	return F;
}
int main() {
	n = read();m = read();
	for(int i = 0;i < n;i ++) {
		a[i].x = read(); a[i].y = read();
	}
	AIput((DP(0,0,0,0,0,0,0)+MOD-1)%MOD,'\n');
	return 0;
}

PS:怎么解决十分烦人的七维数组?不少人使用了“引用”的方法。不过还是OneInDark 的**写法获得最终赢家:

后记

OneInDark在《怎样解题》中说道:如果题目中其它量都极小,只有一个量极大,那么基本就两个办法:数学——矩阵加速,或者数位DP。

现在看来的却如此。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值