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 n≤5,m≤109,∣xi∣,∣yi∣≤4
题解
这次是真不给思路了,我只能说出题人🐂🍺。
数位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。
现在看来的却如此。