CF1290F Making Shapes——数位背包DP

CF1290F Making Shapes

题目描述

一位 怀好意的拉题人的翻译:
在这里插入图片描述

题解

前置知识:计算几何,背包DP,数位DP。

首先容易发现,如果你选的每个向量的系数 c i c_i ci(可能为0)确定了,那么把它们按极角排序后的凸包是唯一确定的,所以我们把问题转换为求不同的系数有多少组。

如果我们把两坐标按正负分开考虑,就会发现
∑ x i > 0 c i x i = ∑ x j < 0 c j ( − x j ) ∑ y i > 0 c i y i = ∑ y j < 0 c j ( − y j ) \sum_{x_i>0}c_ix_i=\sum_{x_j<0}c_j(-x_j)\\ \sum_{y_i>0}c_iy_i=\sum_{y_j<0}c_j(-y_j)\\ xi>0cixi=xj<0cj(xj)yi>0ciyi=yj<0cj(yj)这是因为最终所有线段必须围城闭合的凸多边形,所以向量和为0。而这个 m m m 的限制呢?显然两坐标的极差就等于所有同号的 x x x y y y 的和,也就是说
∑ x i > 0 c i x i ≤ m    且    ∑ y i > 0 c i y i ≤ m \sum_{x_i>0}c_ix_i\le m\,\,且\,\,\sum_{y_i>0}c_iy_i\le m xi>0ciximyi>0ciyim这就是为什么上面的向量和为零要把正负分开写而不是直接求和。

显然,如果 m m m 很小的话,这是可以做背包的。

但是这题 m m m 很大,不能用常规的背包。那怎么办呢,这时一般要放弃背包而想想其它办法…

不,我们可以想办法优化背包!

一般背包的优化有哪些呢?最著名的莫过于倍增优化了。传统的倍增优化是先分开考虑每种物品,然后用倍增拆开它的数量。这里我们把它颠倒一下,先统一用倍增拆开向量的系数,拆成二进制的30位,然后再考虑每个向量的系数在每个二进制位上是否有值。
这时显然是会产生进位的,所以就要从低位到高位考虑。每次转移需要考虑 2 n 2^n 2n 种情况,进位最多有 ∑ x i ≤ 4 n \sum x_i\le4n xi4n,但是这题 n n n x i x_i xi 非常小,所以这么DP居然是可行的。

这就是更高级的倍增优化:考虑进位的数位背包DP

具体地,我们需要记录7个状态(7维DP):
d p [ w ] [ a ] [ b ] [ c ] [ d ] [ p = 0 / 1 ] [ q = 0 / 1 ] dp[w][a][b][c][d][p=0/1][q=0/1] dp[w][a][b][c][d][p=0/1][q=0/1],表示
当前考虑第 w w w 个二进制位,
正的 x i x_i xi 产生的进位为 a a a
负的 x i x_i xi 产生的进位为 b b b
正的 y i y_i yi 产生的进位为 c c c
负的 y i y_i yi 产生的进位为 d d d
p p p ∑ x i > 0 c i x i \sum_{x_i>0}c_ix_i xi>0cixi 的前 w − 1 w-1 w1 位是否 ≤ m \le m m
q q q ∑ y i > 0 c i y i \sum_{y_i>0}c_iy_i yi>0ciyi 的前 w − 1 w-1 w1 位是否 ≤ m \le m m

转移的时候要保证每个二进制位上 ∑ x i > 0 c i x i = ∑ x j < 0 c j ( − x j ) \sum_{x_i>0}c_ix_i=\sum_{x_j<0}c_j(-x_j) xi>0cixi=xj<0cj(xj) ∑ y i > 0 c i y i = ∑ y j < 0 c j ( − y j ) \sum_{y_i>0}c_iy_i=\sum_{y_j<0}c_j(-y_j) yi>0ciyi=yj<0cj(yj),且保证最高位没有进位, p p p q q q 都为1。

最后要减去系数全为0的1种情况,因为要求凸包面积非0。

总复杂度为 O ( 2 n ( 4 n ) 4 log ⁡ m ) O(2^n(4n)^4\log m) O(2n(4n)4logm),写成记忆化搜索会方便一些。

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=-1;
const ll INF=1e18;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[30],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

const ll MOD=998244353;
int n,k,x[10],y[10];
ll m,ans;
inline ll ads(ll x){return x>0?x:-x;}
inline void ad(int&a,int b){a+=b;if(a>=MOD)a-=MOD;}
int dp[32][22][22][22][22][2][2];
inline int DP(int w,int a,int b,int c,int d,bool p,bool q){
	if(w==30)return (!a&&!b&&!c&&!d&&p&&q);
	if(dp[w][a][b][c][d][p][q]>0)return dp[w][a][b][c][d][p][q]-1;
	int&res=dp[w][a][b][c][d][p][q],e=(m>>w)&1;
	for(int s=0;s<(1<<n);s++){
		int aa=a,bb=b,cc=c,dd=d;
		for(int i=1;i<=n;i++)if((s>>(i-1))&1){
			if(x[i]>=0)aa+=x[i];else bb-=x[i];
			if(y[i]>=0)cc+=y[i];else dd-=y[i];
		}if((aa&1)==(bb&1)&&(cc&1)==(dd&1))
			ad(res,DP(w+1,aa>>1,bb>>1,cc>>1,dd>>1,
			(aa&1)<e||((aa&1)==e&&p),(cc&1)<e||((cc&1)==e&&q)));
	}return res++;
}
signed main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)x[i]=read(),y[i]=read();
	print((DP(0,0,0,0,0,1,1)-1+MOD)%MOD);
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值