HNOI2012解题报告
Author: Pengyihao
Day1 T1 双十字
思路
因为矩阵总的大小不超过 1000000 ,所以我们可以预处理往左最多能延续多少,往右、往上、往下……
然后我们考虑枚举双十字中间线所在的列。
枚举下面这根横线所在的行。
然后对于这根横线形成的双十字的数量有影响的行,一定在其之上并且长度短于它。
于是对于每段连续的
1
,我们维护一个
splay 的关键字键值为左右延伸的长度,然后记录每个点往上扩展的长度以及自身的长度能够给予的贡献。
这个贡献可以求一下子树的和。
那么每次计算下面这根横线的贡献的时候,直接找到它在 splay 中的位置,把它左边、右边的区间提取出来,计算贡献即可。
注意必须在第
i
行的时候插入第
代码
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (LL i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (LL i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
using std::vector;
const LL MAXN = 10010, MOD = 1000000009;
vector<LL>line[MAXN];
vector<LL>lr[MAXN], tp[MAXN], co[MAXN];
LL r, c, n;
struct Node {
Node *ch[2], *fa;
LL data1, data2, sum1, sum2, sum3, sz;
void clear();
void update();
void rotate();
void insert(LL, LL);
LL get_rnk(LL);
void splay(Node*);
Node *find_key(LL);
} *treap;
void Node::rotate()
{
Node *pa = fa;
fa = pa -> fa; pa -> fa = this;
if (fa != NULL) {
bool t = (fa -> ch[0] == pa ? 0 : 1);
fa -> ch[t] = this;
}
bool t = (pa -> ch[0] == this ? 0 : 1);
Node *chd = ch[t ^ 1];
ch[t ^ 1] = pa; pa -> ch[t] = chd;
if (chd != NULL) chd -> fa = pa;
pa -> update(); update();
}
void Node::splay(Node *top)
{
while (fa != top) {
if (fa -> fa != top) {
bool t = (fa -> fa -> ch[0] == fa ? 0 : 1);
if (fa -> ch[t] == this) fa -> rotate(), rotate();
else rotate(), rotate();
}
else rotate();
}
if (top == NULL) treap = this;
}
LL Node::get_rnk(LL now)
{
Node *x = this;
LL ret = 0;
while (true) {
if (x -> data1 == now) {
return (x -> ch[0] == NULL ? 1 : x -> ch[0] -> sz + 1) + ret;
}
if (x -> data1 > now) {
if (x -> ch[0] == NULL) return ret;
x = x -> ch[0];
}
else {
if (x -> ch[1] == NULL) return x -> sz + ret;
ret += (x -> ch[0] == NULL ? 1 : x -> ch[0] -> sz + 1);
x = x -> ch[1];
}
}
}
Node* Node::find_key(LL rnk)
{
Node *x = this;
while (true) {
if ((x -> ch[0] == NULL ? 1 : x -> ch[0] -> sz + 1) == rnk) return x;
else if ((x -> ch[0] == NULL ? 1 : x -> ch[0] -> sz + 1) < rnk) {
rnk -= (x -> ch[0] == NULL ? 1 : x -> ch[0] -> sz + 1);
x = x -> ch[1];
}
else x = x -> ch[0];
}
}
void Node::insert(LL now, LL now2)
{
if (treap == NULL) {
Node *hr = new Node;
hr -> ch[0] = hr -> ch[1] = hr -> fa = NULL;
hr -> data1 = now; hr -> data2 = now2; treap = hr; treap -> update();
return;
}
LL rnk = treap -> get_rnk(now);
if (rnk == 0) {
Node *hr = new Node;
hr -> ch[0] = hr -> fa = NULL;
hr -> ch[1] = treap; hr -> data1 = now; hr -> data2 = now2;
treap -> fa = hr; hr -> update();
treap = hr;
}
else if (rnk == treap -> sz) {
Node *hr = new Node;
hr -> ch[1] = hr -> fa = NULL;
hr -> ch[0] = treap; hr -> data1 = now; hr -> data2 = now2;
treap -> fa = hr; hr -> update();
treap = hr;
}
else {
Node *hr = new Node;
treap -> find_key(rnk) -> splay(NULL);
treap -> find_key(rnk + 1) -> splay(treap);
hr -> data1 = now;
hr -> data2 = now2;
hr -> ch[0] = NULL;
hr -> ch[1] = treap -> ch[1];
hr -> fa = treap; treap -> ch[1] = hr;
hr -> ch[1] -> fa = hr; hr -> update(); treap -> update();
}
}
void Node::clear()
{
if (ch[0] != NULL) ch[0] -> clear();
if (ch[1] != NULL) ch[1] -> clear();
delete this;
}
void Node::update()
{
sz = 1;
sum1 = data1 * data2 % MOD;
sum2 = (data1 * (data1 + 1) / 2) * data2 % MOD;
sum3 = data2;
if (ch[0] != NULL) {
sz += ch[0] -> sz;
sum1 = (sum1 + ch[0] -> sum1) % MOD;
sum2 = (sum2 + ch[0] -> sum2) % MOD;
sum3 = (sum3 + ch[0] -> sum3) % MOD;
}
if (ch[1] != NULL) {
sz += ch[1] -> sz;
sum1 = (sum1 + ch[1] -> sum1) % MOD;
sum2 = (sum2 + ch[1] -> sum2) % MOD;
sum3 = (sum3 + ch[1] -> sum3) % MOD;
}
}
int main()
{
freopen("cross.in", "r", stdin);
freopen("cross.out", "w", stdout);
in(r); in(c); in(n);
FOR(i, 0, r + 1) {
lr[i].resize(c + 2);
tp[i].resize(c + 2);
co[i].resize(c + 2);
line[i].resize(c + 2);
}
FOR(i, 1, r) FOR(j, 1, c) line[i][j] = 1;
FOR(i, 1, n) {
LL x, y;
in(x); in(y);
line[x][y] = 0;
}
FOR(i, 1, r) {
FOR(j, 1, c) {
if (line[i][j] == 0) continue;
if (j == 1 || line[i][j - 1] == 0) lr[i][j] = 0;
else lr[i][j] = lr[i][j - 1] + 1;
}
DNF(j, c, 1) {
if (line[i][j] == 0) continue;
if (j == c || line[i][j + 1] == 0) lr[i][j] = 0;
else chkmin(lr[i][j], lr[i][j + 1] + 1);
}
FOR(j, 1, c) if (line[i][j]) {
if (i == 1 || !line[i - 1][j]) tp[i][j] = 0;
else tp[i][j] = tp[i - 1][j] + 1;
}
}
DNF(i, r, 1) {
FOR(j, 1, c) if (line[i][j]) {
if (i == r || !line[i + 1][j]) co[i][j] = 0;
else co[i][j] = co[i + 1][j] + 1;
}
}
treap = NULL;
bool is_cleared = true;
LL ans = 0;
FOR(j, 1, c) {
if (!is_cleared) {
treap -> clear();
treap = NULL;
is_cleared = true;
}
FOR(i, 1, r) {
if (i > 2) {
if (lr[i - 2][j] != 0 && line[i - 1][j]) {
treap -> insert(lr[i - 2][j], tp[i - 2][j]);
is_cleared = false;
}
}
if (!line[i][j]) {
if (!is_cleared) {
treap -> clear();
treap = NULL;
is_cleared = true;
}
continue;
}
LL now = lr[i][j];
if (now && treap != NULL) {
LL rnk = treap -> get_rnk(now);
if (rnk != 0) {
Node *hr;
if (rnk != treap -> sz) {
treap -> find_key(rnk + 1) -> splay(NULL);
treap -> find_key(rnk) -> splay(treap);
hr = treap -> ch[0];
}
else {
treap -> find_key(rnk) -> splay(NULL);
hr = treap;
}
LL fst = hr -> sum1 * now % MOD * co[i][j] % MOD;
LL sec = hr -> sum2 * co[i][j] % MOD;
ans = (ans + fst - sec) % MOD;
}
if (rnk != treap -> sz) {
Node *hr;
if (rnk != 0) {
treap -> find_key(rnk) -> splay(NULL);
treap -> find_key(rnk + 1) -> splay(treap);
hr = treap -> ch[1];
}
else {
treap -> find_key(rnk + 1) -> splay(NULL);
hr = treap;
}
LL valu =
(now * now - now * (now + 1) / 2) * co[i][j] % MOD;
ans = (ans + hr -> sum3 * valu % MOD) % MOD;
}
}
}
}
printf("%lld\n", ans);
return 0;
}
Day1 T2 与非
思路
首先可以发现一个性质——NAND操作可以构成所有的位运算,这个手玩一下就出来了。
然后对于二进制的某两位,我们发现如果对于每一个操作数,他们的这两位都相同,那么不论怎么运算最后肯定还是相同的。
除了这种限制之外,就没有其它限制了。
意思是说,如果在必定相同的两位之间连一条边,那么会形成一个个联通块。联通块与联通块之间两两对于答案的贡献是独立的。
联通块可以用并查集维护。
于是就可以答案进行数位DP辣!!
对于答案的第 i 位,它的取值会影响一个联通块的取值。
我们从高到低地做,如果边界的该位上为
其余的类似。
代码
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(), f = 1; x = 0;
while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if (ch == '-') f = -1, ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f;
}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
const LL MAXN = 1010;
LL n, k, l, r, tot, A[MAXN], fa[MAXN];
LL find(LL x)
{
LL tmp = x, pre;
while (tmp != fa[tmp]) tmp = fa[tmp];
while (x != tmp) pre = fa[x], fa[x] = tmp, x = pre;
return tmp;
}
void merge(LL x, LL y)
{
LL fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy;
}
LL query(LL x)
{
if (x < 0) return 0;
LL ret = 0, tmp = tot;
static bool chose[100];
static bool how_chose[100];
memset(chose, false, sizeof chose);
if (x > (1ll << (k)) - 1) return (1ll << tmp);
DNF(i, k, 1) {
if (!(x & (1ll << (i - 1)))) {
LL fx = find(i);
if (chose[fx]) {
if (how_chose[fx] == 1) return ret;
}
else {
chose[fx] = true;
how_chose[fx] = 0;
tmp--;
continue;
}
}
else {
LL fx = find(i);
if (chose[fx]) {
if (how_chose[fx] == 1) continue;
else {
ret += (1ll << tmp);
return ret;
}
}
else {
chose[fx] = true;
how_chose[fx] = 1;
tmp--;
ret += (1ll << tmp);
continue;
}
}
}
return ret + 1;
}
int main()
{
freopen("nand.in", "r", stdin);
freopen("nand.out", "w", stdout);
in(n); in(k); in(l); in(r);
FOR(i, 1, n) in(A[i]);
FOR(i, 1, k) fa[i] = i;
FOR(i, 1, k) {
LL tmp = (1ll << (k)) - 1;
FOR(j, 1, n) {
if (A[j] & (1ll << (i - 1))) {
tmp &= A[j];
}
else tmp &= (A[j] ^ ((1ll << k) - 1));
}
if (!(tmp & (1ll << (i - 1)))) {
puts("WA");
}
FOR(j, 1, k) if (j != i) {
if (bool(tmp & (1ll << (j - 1))) == bool(tmp & (1ll << (i - 1))))
merge(i, j);
}
}
FOR(i, 1, k) if (fa[i] == i) tot++;
printf("%lld\n", query(r) - query(l - 1));
return 0;
}
Day1 T3 排队
思路
首先放男同学,有
然后放老师,可以放到一起或分开放,方案分别为 n!×P2n+1×Pmn+3 ,和 n!×2(n+1) 。
最后放女同学。如果老师放在一起了,那么就要放一个女同学在老师中间;否则把老师看作男同学。
总方案为
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define Max(x,y) ((x)>(y)?(x):(y))
#define LL long long
#define MOD 100000000
using namespace std;
struct bign{
int len;
LL s[10000];
bign(){
len=1;
memset(s,0,sizeof s);
s[1]=1;
}
bign operator = (const LL &num){
len=1;
s[1]=num;
return *this;
}
bign operator + (const bign&num){
bign c;c.s[1]=0;c.len=Max(num.len,len);
for(int i=1;i<=c.len;i++){
c.s[i+1]=(c.s[i]+s[i]+num.s[i])/MOD;
c.s[i]=(c.s[i]+s[i]+num.s[i])%MOD;
}
if(c.s[c.len+1])c.len++;
return c;
}
bign operator - (const bign&num){
bign c;c.s[1]=0;c.len=len;
int x=0;
for(int i=1;i<=c.len;i++){
c.s[i]=s[i]-num.s[i]+x;
x=0;
if(c.s[i]<0){
c.s[i]+=MOD;
x=-1;
}
}
while(!c.s[c.len]&&c.len>1)c.len--;
return c;
}
bign operator * (const LL&num){
bign c;c.s[1]=0;c.len=len;
for(int i=1;i<=c.len;i++){
c.s[i+1]=(c.s[i]+s[i]*num)/MOD;
c.s[i]=(c.s[i]+s[i]*num)%MOD;
}
if(c.s[c.len+1])c.len++;
return c;
}
bign operator * (const bign&num){
bign c;c.s[1]=0;c.len=len+num.len+1;
for(int i=1;i<=len;i++)
for(int j=1;j<=num.len;j++){
c.s[i+j]+=(c.s[i+j-1]+s[i]*num.s[j])/MOD;
c.s[i+j-1]=(c.s[i+j-1]+s[i]*num.s[j])%MOD;
}
while(!c.s[c.len]&&c.len>1)c.len--;
return c;
}
void out(){
for(int i=len;i>=1;i--){
if(i==len)printf("%lld",s[i]);
else printf("%08lld",s[i]);
}
}
};
LL n,m,tmp,tmp2;
bign ans1,ans2;
int main(){
freopen("queue.in", "r", stdin);
freopen("queue.out", "w", stdout);
scanf("%lld%lld",&n,&m);tmp=n+4;tmp2=n+3;
for(LL i=1;i<=n;i++){
ans1=ans1*i;
ans2=ans2*i;
}
ans1=ans1*(n+1)*n;
for(LL i=1;i<=m&&tmp>=0;i++){
tmp--;
ans1=ans1*tmp;
}
for(LL i=1;i<=m-1&&tmp2>=0;i++){
tmp2--;
ans2=ans2*tmp2;
}
ans2=ans2*2*m*(n+1);
(ans1+ans2).out();
return 0;
}
Day1 T4 矿场搭建
思路
首先双联通缩点,然后对于每个联通块,只需要每个叶子节点放一个出口就行了(不能放割点)。
方案个数的话乘法原理就好了。
注意只有一个双联通分量的情况要特判!!
代码
#include <bits/stdc++.h>
const int MAXN = 1010, MAXM = 510;
bool is[MAXN];
int belong[MAXN];
int dfn[MAXN], low[MAXN], INDEX, dot[MAXN], all, sz[MAXN];
int n, cnt, head[MAXN], nxt[MAXM << 1], data[MAXM << 1], du[MAXN];
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch != EOF && (ch < '0' || ch > '9')) ch = getchar();
if (ch == EOF) exit(0);
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
void add(int x, int y) {
nxt[cnt] = head[x]; data[cnt] = y; head[x] = cnt++;
nxt[cnt] = head[y]; data[cnt] = x; head[y] = cnt++;
}
void dfs(int now, int rot, int fa) {
int size = 0; bool flag = false;
dfn[now] = low[now] = ++INDEX;
for (int i = head[now]; i != -1; i = nxt[i])
if (!dfn[data[i]]) {
size++;
dfs(data[i], rot, now);
low[now] = Min(low[now], low[data[i]]);
if (low[data[i]] >= dfn[now]) flag = true;
}
else if (data[i] != fa) {
low[now] = Min(low[now], dfn[data[i]]);
}
if (flag && (now != rot || size >= 2)) {
dot[++dot[0]] = now;
is[now] = true;
}
}
void dfs2(int now) {
dfn[now] = 1;
for (int i = head[now]; i != -1; i = nxt[i])
if (!is[data[i]] && !dfn[data[i]]) {
dfs2(data[i]);
sz[all]++;
belong[data[i]] = all;
}
}
int TTT;
int main() {
freopen("mining.in", "r", stdin);
freopen("mining.out", "w", stdout);
while (true) {
in(n);
int dian = -1;
if (!n) return 0;
cnt = 0; all = 0;
memset(du, 0, sizeof du);
memset(is, 0, sizeof is);
memset(dot, 0, sizeof dot);
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof low);
memset(head, -1, sizeof head);
memset(belong, 0, sizeof belong);
for (int i = 1; i <= n; i++) {
int s, t;
in(s); in(t); add(s, t);
dian = std::max(dian, std::max(s, t));
}
for (int i = 1; i <= dian; i++)
if (!dfn[i]) dfs(i, i, -1);
memset(dfn, 0, sizeof dfn);
for (int i = 1; i <= dian; i++)
if (!dfn[i] && !is[i]) {
belong[i] = ++all;
sz[all] = 1;
dfs2(i);
}
if (all == 1) {
printf("Case %d: %d %d\n", ++TTT, 2, dian * (dian - 1) / 2);
continue;
}
for (int i = 1; i <= dian; i++)
if (is[i]) {
for (int j = head[i]; j != -1; j = nxt[j])
dfn[belong[data[j]]] = 0;
for (int j = head[i]; j != -1; j = nxt[j])
if (!dfn[belong[data[j]]]) {
dfn[belong[data[j]]] = true;
du[belong[data[j]]]++;
}
}
int tot = 0;
long long fang = 1;
for (int i = 1; i <= all; i++) {
if (du[i] == 1) {
tot++;
if (sz[i] > 1)
fang = fang * (sz[i]);
}
}
printf("Case %d: %d %lld\n", ++TTT, tot, fang);
}
return 0;
}
Day2 T1
这是一道计算几何的题目。
因为我还没有进行这个专题,所以我跳过了这道题目。
Day2 T2
这是一道计算几何的题目。
因为我还没有进行这个专题,所以我跳过了这道题目。
Day2 T3
思路
这个题目是一道 splay 的启发式合并的模板题。
只需要从小的合并到大的中间就可以了。
代码
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MAXN = 100010;
char command[10];
int n, m, rnk[MAXN], fa[MAXN];
struct Node {
int data, sz, id;
Node *ch[2], *fa;
void update();
void splay(Node*);
void rotate();
Node *find_key(int);
void insert(Node*);
} *to[MAXN];
void Node::rotate()
{
Node *pa = fa;
fa = pa -> fa;
if (fa != NULL) {
bool t = fa -> ch[0] == pa ? 0 : 1;
fa -> ch[t] = this;
}
pa -> fa = this;
bool t = pa -> ch[0] == this ? 0 : 1;
Node *chd = ch[t ^ 1];
pa -> ch[t] = chd;
if (chd != NULL) chd -> fa = pa;
ch[t ^ 1] = pa;
pa -> update(); update();
}
void Node::splay(Node *top)
{
while (fa != top) {
if (fa -> fa != top) {
bool t = (fa -> ch[0] == this ? 0 : 1);
if (fa -> fa -> ch[t] == fa) {
fa -> rotate(); rotate();
}
else rotate(), rotate();
}
else rotate();
}
}
Node* Node::find_key(int rnk)
{
Node *hr = this;
while (true) {
int nowrnk;
nowrnk = (hr -> ch[0] == NULL ? 1 : hr -> ch[0] -> sz + 1);
if (nowrnk == rnk) return hr;
if (nowrnk < rnk) {
rnk -= nowrnk;
hr = hr -> ch[1];
}
else hr = hr -> ch[0];
}
}
void Node::update()
{
sz = 1;
if (ch[0] != NULL) sz += ch[0] -> sz;
if (ch[1] != NULL) sz += ch[1] -> sz;
}
void Node::insert(Node *x)
{
bool t;
Node *pos = this, *pa = NULL;
while (pos != NULL) {
pa = pos;
if (x -> data > pos -> data) pos = pos -> ch[1], t = 1;
else pos = pos -> ch[0], t = 0;
}
x -> fa = pa;
if (pa != NULL) pa -> ch[t] = x;
x -> ch[0] = x -> ch[1] = NULL;
while (x != NULL) {
x -> update();
x = x -> fa;
}
}
void Ins(Node* now, Node* to)
{
if (now == NULL) return;
Ins(now -> ch[0], to); Ins(now -> ch[1], to);
to -> splay(NULL); to -> insert(now); now -> splay(NULL);
}
int find(int x)
{
int tmp = x, pre;
while (tmp != fa[tmp]) tmp = fa[tmp];
while (x != tmp) pre = fa[x], fa[x] = tmp, x = pre;
return tmp;
}
bool merge(int x, int y)
{
int fx = find(x), fy = find(y);
return fx == fy ? false : fa[fx] = fy, true;
}
int main()
{
freopen("neverland.in", "r", stdin);
freopen("neverland.out", "w", stdout);
in(n); in(m);
FOR(i, 1, n) fa[i] = i;
FOR(i, 1, n) in(rnk[i]);
FOR(i, 1, n) {
to[i] = new Node;
to[i] -> data = rnk[i];
to[i] -> sz = 1; to[i] -> id = i;
to[i] -> fa = to[i] -> ch[0] = to[i] -> ch[1] = NULL;
}
FOR(i, 1, m) {
int x, y; in(x); in(y);
if (merge(x, y)) {
if (to[x] == NULL) printf("%d\n", x);
to[x] -> splay(NULL);
to[y] -> splay(NULL);
if (to[x] -> sz < to[y] -> sz) {
Ins(to[x], to[y]);
}
else Ins(to[y], to[x]);
}
}
int q; in(q);
while (q--) {
scanf("%s", command);
if (command[0] == 'Q') {
int x, k;
in(x); in(k);
to[x] -> splay(NULL);
if (to[x] -> sz < k) printf("-1\n");
else {
printf("%d\n", to[x] -> find_key(k) -> id);
}
}
else {
int x, y;
in(x); in(y);
if (merge(x, y)) {
to[x] -> splay(NULL);
to[y] -> splay(NULL);
if (to[x] -> sz < to[y] -> sz) {
Ins(to[x], to[y]);
}
else Ins(to[y], to[x]);
}
}
}
return 0;
}
Day2 T4 集合选数
思路
考虑一张表,将 1 在左下角。
满足一个性质:对于一个格子,它的右边的格子里的数字是它的
于是问题转化为在格子中选不相邻的数的方案数。
表的长宽不会很大,是
可能有很多张不相交的表。
状压dp就好了。
代码
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (register int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (register int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MOD = 1000000001;
const int MAXN = 100010;
bool vis[MAXN];
int n, ans = 1;
int map[18][18], end[18], f[2][MAXN << 1];
void solve(int now)
{
int ret = 0;
FOR(i, 1, 100000) {
if (i == 1) map[1][i] = now;
else map[1][i] = map[1][i - 1] * 2;
if (map[1][i] > n) {
//map[1][i] = -1;
break;
}
vis[map[1][i]] = true;
FOR(j, 2, 100000) {
map[j][i] = map[j - 1][i] * 3;
if (map[j][i] > n) {
//map[j][i] = -1;
break;
}
vis[map[j][i]] = true;
}
}
end[0] = 0;
bool t = 0;
int all = 1;
f[t ^ 1][0] = 1;
bool first = true;
FOR(i, 1, 100000) {
if (map[i][1] <= n) {
FOR(j, 1, 100000) if (map[i][j] > n) {end[i] = j - 1; break;}
int limits1 = (1 << end[i]) - 1, limits2 = (1 << end[i - 1]) - 1;
FOR(j, 0, limits1) {
f[t][j] = 0;
bool flag = true;
if (!first && !f[t ^ 1][j]) continue;
FOR(l, 2, end[i]) if ((j & (1 << (l - 1))) && (((j) & (1 << (l - 2))))) {
flag = false;
break;
}
if (!flag) continue;
FOR(k, 0, limits2) {
bool flag = true;
if (!f[t ^ 1][k]) continue;
FOR(l, 1, end[i]) {
if ((j & (1 << (l - 1))) && (k & (1 << (l - 1)))) {
flag = false;
break;
}
}
if (flag) f[t][j] = (f[t][j] + f[t ^ 1][k]) % MOD;
}
}
}
else {
all = i - 1;
break;
}
first = false;
t ^= 1;
}
FOR(i, 0, (1 << (end[all] - 1))) ret = (ret + f[t ^ 1][i]) % MOD;
ans = 1ll * ans * ret % MOD;
}
int main()
{
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
in(n);
FOR(i, 1, n) {
if (!vis[i]) solve(i);
}
printf("%d\n", ans);
return 0;
}