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>0∑cixi=xj<0∑cj(−xj)yi>0∑ciyi=yj<0∑cj(−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>0∑cixi≤m且yi>0∑ciyi≤m这就是为什么上面的向量和为零要把正负分开写而不是直接求和。
显然,如果 m m m 很小的话,这是可以做背包的。
但是这题 m m m 很大,不能用常规的背包。那怎么办呢,这时一般要放弃背包而想想其它办法…
不,我们可以想办法优化背包!
一般背包的优化有哪些呢?最著名的莫过于倍增优化了。传统的倍增优化是先分开考虑每种物品,然后用倍增拆开它的数量。这里我们把它颠倒一下,先统一用倍增拆开向量的系数,拆成二进制的30位,然后再考虑每个向量的系数在每个二进制位上是否有值。
这时显然是会产生进位的,所以就要从低位到高位考虑。每次转移需要考虑
2
n
2^n
2n 种情况,进位最多有
∑
x
i
≤
4
n
\sum x_i\le4n
∑xi≤4n,但是这题
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
w−1 位是否
≤
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
w−1 位是否
≤
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;
}