[CQOI2012]局部极小值

嘟嘟嘟


谁说CQOI的题都是板儿题,我就觉得这题挺难的……


看到数据范围这么小,就会想状压。然而\(2 ^ {28}\)肯定过不了。不过对于所有的极小值的格子,最多不会超过8个,所以我们状压选了哪些局部极小值的格子(坑儿)。


然后我们从小到大填数,那么对于一个数\(i\),他无非就两种填法:填入一个坑,或是填坑以外的点。
填一个坑儿就是直接填,于是有\(dp[i][S] = \sum dp[i - 1][S\) ^ \((1 << k)]\)。
不填坑的话,得考虑填上这个数之后,那些还没有填的坑仍能保证是坑,即不能往他们的四周填。因此我们预处理出来对于当前每一个状态,可以填的格子数目\(num[S]\)。然后有转移方程\(dp[i][S] = dp[i - 1][S] * (num[S] - (i - 1))\)。
答案就是\(dp[n * m][(1 << tot) - 1]\),\(tot\)是坑的总数。


但光这样是不对的。因为一些不是坑的点填完后可能成为了坑,换句话说,我们上面的\(dp[n * m][(1 << tot) -1]\)表示的是至少有\(tot\)个坑时的方案数,而我么需要的是恰好有这么多坑的方案数。
那么就能想到容斥了!我们暴力枚举棋盘上哪些格子可以是坑,然后每构造一个棋盘,就dp一次,然后看看是加上还是减去。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<assert.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const ll mod = 12345678;
In ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
In void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
  freopen(".in", "r", stdin);
  freopen(".out", "w", stdout);
#endif
}

int n, m, TOT;
char s[5][8];
ll ans = 0;

In ll inc(ll a, ll b) {return a + b < mod ? a + b : a + b - mod;}

struct Node
{
  int x, y;
}t[30];
int tot = 0, num[(1 << 8) + 5];
bool vis[5][8];
ll dp[30][(1 << 8) + 5];
const int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
In void calc()
{
  tot = 0;
  for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= m; ++j)
      if(s[i][j] == 'X') t[++tot] = (Node){i, j};
  for(int i = 0; i < (1 << tot); ++i)
    {
      Mem(vis, 0);
      for(int j = 1; j <= tot; ++j)
    if(!((i >> (j - 1)) & 1))
      {
        vis[t[j].x][t[j].y] = 1;
        for(int k = 0; k < 8; ++k)
          {
        int nx = t[j].x + dx[k], ny = t[j].y + dy[k];
        if(nx && nx <= n && ny && ny <= m) vis[nx][ny] = 1;
          }
      }
      int cnt = 0;
      for(int j = 1; j <= n; ++j)
    for(int k = 1; k <= m; ++k) cnt += vis[j][k];
      num[i] = n * m - cnt;
    }
  Mem(dp, 0); dp[0][0] = 1;
  for(int i = 1; i <= n * m; ++i)
    {
      for(int S = 0; S < (1 << tot); ++S)
    {
      dp[i][S] = inc(dp[i][S], dp[i - 1][S] * (num[S] - i + 1) % mod);
      for(int j = 0; j < tot; ++j)
        if((S >> j) & 1) dp[i][S] = inc(dp[i][S], dp[i - 1][S ^ (1 << j)]);
    }
    }
  ll tp = dp[n * m][(1 << tot) - 1];
  ans = inc(ans, ((tot - TOT) & 1) ? mod - tp: tp);
}

In void dfs(int x, int y)
{
  if(x == n + 1) {calc(); return;}
  if(y == m) dfs(x + 1, 1);
  else dfs(x, y + 1);
  if(s[x][y] == 'X') return;
  bool flg = 1;
  for(int i = 0; i < 8 && flg; ++i)
    {
      int nx = x + dx[i], ny = y + dy[i];
      if(nx && nx <= n && ny && ny <= m)
    if(s[nx][ny] == 'X') flg = 0;
    }
  if(!flg) return;
  s[x][y] = 'X';
  if(y == m) dfs(x + 1, 1);
  else dfs(x, y + 1);
  s[x][y] = '.';
}

int main()
{
  //MYFILE();
  n = read(), m = read();
  for(int i = 1; i <= n; ++i) scanf("%s", s[i] + 1);
  for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= m; ++j) TOT += (s[i][j] == 'X');
  dfs(1, 1);
  write(ans), enter;
  return 0;
}

原文地址:https://www.cnblogs.com/mrclr/p/10943389.html

时间: 2024-11-12 15:32:21

[CQOI2012]局部极小值的相关文章

2669[cqoi2012]局部极小值 容斥+状压dp

2669: [cqoi2012]局部极小值 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 774  Solved: 411[Submit][Status][Discuss] Description 有一个n行m列的整数矩阵,其中1到nm之间的每个整数恰好出现一次.如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值. 给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵. Input 输入第一行包含两个整数

P3160 [CQOI2012]局部极小值

题目 P3160 [CQOI2012]局部极小值 一眼就是状压,接下来就不知道了\(qwq\) 做法 我们能手玩出局部小值最多差不多是\(8,9\)个的样子,\(dp_{i,j}\)为填满\(1~i\)数字,局部小值的状态为\(j\) 第\(k\)个局部极小值填\(i\):\(dp[i][j]=(dp[i][j]+dp[i-1][j^(1<<k-1)])%p\) 不填在局部极小值,显然有些地方不能填\(i\)的,首先还没填的局部极小值不填,其周围也不能填(填\(i\)后后面再填比不符合局部极小

BZOJ 2669 cqoi2012 局部极小值 状压DP+容斥原理

题目大意:给定一个n?m的矩阵,标记出其中的局部极小值,要求填入1...n?m,求方案数 <多年的心头大恨终于切掉了系列> 考虑将数字从小到大一个一个填进去 由于局部极小值最多8个,我们可以状压DP 令fi,j表示已经填完了前i个数,局部极小值的填充状态为j的方案数 预处理出cntj表示填充状态为j时共有多少位置是可以填充的(包括已填充的局部极小值位置) 那么有DP方程fi,j=fi?1,j?C1cntj?i+1+∑k∈jfi?1,j?{k} 但是问题是这样虽然保证了标记的位置都是局部最小值,

【bzoj2669】 cqoi2012—局部极小值

http://www.lydsy.com/JudgeOnline/problem.php?id=2669 (题目链接) 题意 给出一个$n*m$的整数矩阵,其中$[1,nm]$中的整数每个出现一次,有一些位置为局部最小值.问方案数. Solution 好神的dp啊. http://blog.csdn.net/popoqqq/article/details/48028773 $cnt_j$表示的是,在局部最小值被填充的状态为$j$的情况下,目前有多少个位置可以填,这些位置中包括已经被填了数的位置.

●BZOJ 2669 [cqoi2012]局部极小值

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2669 题解: 容斥,DP,DFS 先看看 dp 部分:首先呢,X的个数不会超过 8个.个数很少,所以考虑状压,把需要填 X的那几个位置状压为二进制10表示对应的那个X位置是否已经填数.同时填的数互不重复,考虑从小填到大. 令 cnt[S] 表示除了不在集合 S 里的 X 位置及其周围的位置,剩下的位置个数. 定义 dp[i][S]表示从小到大填数填完了i这个数,且已经填了的 S 这个集合里

bzoj2669 [cqoi2012]局部极小值 状压DP+容斥

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=2669 题解 可以发现一个 \(4\times 7\) 的矩阵中,有局部最小值的点最多有 \(2\times 4 = 8\) 个,所以我们可以状压一下每个局部最小值的位置有没有被选. 从小到大填入每一个格子,那么如果一个点的周围有没有被填上的局部最小值,那么这个格子不可以被填.所以预处理一下每种状态下可以自由填多少格子,然后如果状态保持不变的话,就可以这样转移. 如果状态变化,就是说填了一个局

2016 CCPC 网络赛 B 高斯消元 C 树形dp(待补) G 状压dp+容斥(待补) H 计算几何

2016 CCPC 网络赛 A - A water problem 水题,但读题有个坑,输入数字长度很大.. B - Zhu and 772002 题意:给出n个数(给出的每个数的质因子最大不超过2000),选出多个数相乘得b.问有多少种选法让b 为完全平方数. tags:高斯消元,求异或方程组解的个数.   好题 每个数先素数分解开.  对于2000以内的每个素数p[i],这n个数有奇数个p[i]则系数为1,偶数个则系数为0,最后n个数的p[i]系数异或和都要为0才会使得最后的积为完全平方数.

计数类问题专题

主要是前两天被uoj的毛爷爷的题虐的不轻,心里很不爽啊,必须努力了,, 计数类问题分为:1.组合数学及数论计数 2.dp:状态压缩dp,插头轮廓线dp,树形dp,数位dp,普通dp 3.容斥原理 4.polya原理 5.图论计数 6.生成函数 7.其它(生成树计数等等) 本文主要研究前3个内容 考虑基本计数原理:加法原理,减法原理,乘法原理,除法原理 计数的基本原则:结果不重不漏 加法原理比较自然,中间过程有时减法原理 考虑到无向,有向图的各种量值(生成树之类)计数,状态压缩dp解决 论文:ht

【BZOJ2666】[cqoi2012]组装 贪心

[BZOJ2666][cqoi2012]组装 Description 数轴上有m个生产车间可以生产零件.一共有n种零件,编号为1~n.第i个车间的坐标为xi,生产第pi种零件(1<=pi<=n).你需要在数轴上的某个位置修建一个组装车间,把这些零件组装起来.为了节约运输成本,你需要最小化cost(1)+cost(2)+…+cost(n),其中cost(x)表示生产第x种零件的车间中,到组装车间距离的平方的最小值. Input 输入第一行为两个整数n, m,即零件的种类数和生产车间的个数.以下m