您的位置:首页 > 大数据 > 人工智能

【HDU5743 2016 Multi-University Training Contest 2J】【dfs展开式DP 前缀和思想】Join The Future 40个数已知区间和为奇或偶输出方案

2016-07-28 10:39 555 查看

Join The Future

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)

Total Submission(s): 118    Accepted Submission(s): 43


[align=left]Problem Description[/align]
Professor Zhang has an array of n integers.
He writes down some properties about the array in the paper. Each property is described by three integers li, ri and si,
which means that the sum of elements modulo 2 on interval [li,ri] of
the array is equal to si.

After that, he tries to recover the array only using the above properties. Apparently, there are many such arrays. So, Professor Zhang decides to limits the lower bound and upper bound of each integer in the array.

Given the properties, the lower bounds and the upper bounds, find the number of possible arrays and the lexicographically smallest array.
 

[align=left]Input[/align]
There are multiple test cases. The first line of input contains an integer T,
indicating the number of test cases. For each test case:

The first line contains two integers n and m (1≤n≤40,0≤m≤n(n+1)2) --
the length off array and the number of properties.

Each of the next n lines
contains two integers xi and yi (0≤xi≤yi≤109) --
the lower bound and upper bound of the i-th
integer.

Each of the next m lines
contains three integers li, ri and si (1≤li≤ri≤n,0≤si≤1) denoting
the i-th
property.
 

[align=left]Output[/align]
For each case, output the number of possible arrays in the first line. As the value could be very large, print it modulo 109+7.
Then, output the lexicographically smallest array in the second line. If the number of possible arrays equals to zero, just output "-1" (without the quotes) in the second line.
 

[align=left]Sample Input[/align]

3
3 3
1 10
0 21
3 15
2 2 1
3 3 0
2 3 1
3 0
0 1
1 3
3 4
3 3
1 10
0 21
3 3
2 2 1
3 3 0
2 3 1

 

[align=left]Sample Output[/align]

660
1 1 4
12
0 1 3
0
-1

 

[align=left]Author[/align]
zimpha
 

[align=left]Source[/align]
2016 Multi-University Training
Contest 2
 

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 42, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m;
LL b
; //对应二进制,b[i] = 1ll << i
int l
, r
; //记录对于每个点,其可以选数的区间范围
LL cnt
[2]; //记录对于每个点,其可以选择奇数或偶数的方案数
vector< pair<int, int> >a
; //存边,用于找到状态之间的制约关系
int vis
; //确定每个点的遍历状态,从而找出制约关系
LL con
; //con[i]表示在i状态确定的条件下,哪些点的状态是确定的
LL odd
[2]; //odd[i][j]表示在i状态为偶数(j==0)或奇数(j==1)的条件下,哪些点的状态确定为奇数
int f
[2]; //记录每次dfs的状态
int ans; //记录答案方案数
int way
; //记录最小字典序的解
void add(int &x, int y)
{
if ((x += y) >= Z)x -= Z;
}
void update(LL vis, LL sta)
{
LL pre = 0;
bool better = 0;
for (int i = 1; i <= n; ++i)
{
int now;
if (vis & b[i])
{
now = l[i] + ((l[i] & 1) != ((sta >> i & 1) ^ pre));
}
else now = l[i];
if (better)way[i] = now;
else
{
if (now > way[i])return;
if (now < way[i])
{
better = 1;
way[i] = now;
}
}
pre = pre ^ (now & 1);
}
}
void dfs(int p, LL vis, LL sta)
{
if (p > n)
{
int tmp = 0;
for (int i = 0; i < 2; ++i)
{
if ((vis & b
) && (sta >> n & 1) != i)continue;
add(tmp, f
[i]);
}
if (tmp)update(vis, sta), add(ans, tmp);
return;
}
f[p][0] = f[p][1] = 0;
for (int i = 0; i < 2; ++i) //枚举当前该点的前缀和状态
{
if ((vis & b[p]) && (sta >> p & 1) != i)continue;
for (int j = 0; j < 2; ++j) //枚举上一个点的前缀和状态
{
if ((vis & b[p - 1]) && (sta >> p - 1 & 1) != j)continue;
add(f[p][i], f[p - 1][j] * cnt[p][i ^ j] % Z);
}
}
if (a[p].empty())dfs(p + 1, vis, sta);
else
{
for (int i = 0; i < 2; ++i)if (f[p][i])
{
dfs(p + 1, vis | con[p], sta | odd[p][i]);
}
}
}

void solve()
{
//找制约关系
for (int i = 0; i <= n; ++i)
{
con[i] = odd[i][0] = odd[i][1] = 0;
MS(vis, -1); queue<int>q;
vis[i] = 1; q.push(i);
while (!q.empty())
{
int x = q.front(); q.pop();
con[i] |= b[x];
odd[i][vis[x]] |= b[x];
for (int j = a[x].size() - 1; ~j; --j)
{
int y = a[x][j].first; int s = a[x][j].second;
if (~vis[y])
{
if ((vis[x] ^ vis[y]) != s)
{
puts("0"); puts("-1");
return;
}
}
else
{
vis[y] = vis[x] ^ s; q.push(y);
}
}
}
}
//求方案数与最优方案
for (int i = 1; i <= n; ++i)way[i] = Z; ans = 0;
f[0][0] = 1; dfs(1, con[0], odd[0][0]);
if (way[1] == Z) { puts("0"); puts("-1"); }
else
{
printf("%d\n", ans);
for (int i = 1; i <= n; ++i)printf("%d%c", way[i], " \n"[i == n]);
}
}
int main()
{
for (int i = 0; i < N; ++i)b[i] = 1ll << i;
scanf("%d", &casenum);
for (casei = 1; casei <= casenum; ++casei)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &l[i], &r[i]);
int x = l[i] & 1; int y = x ^ 1;
cnt[i][x] = (r[i] - l[i]) / 2 + 1;
cnt[i][y] = (r[i] - l[i] + 1) - cnt[i][x];
}
for (int i = 0; i <= n; ++i)a[i].clear();
for (int i = 1; i <= m; ++i)
{
int l, r, s;
scanf("%d%d%d", &l, &r, &s); --l;
a[l].push_back(MP(r, s));
a[r].push_back(MP(l, s));
}
solve();
}
return 0;
}
/*
【题意】
有n(40)个数a[],每个数都未知,不过对应着一个选数的上下界bot[],top[],就是a[i]的选数范围为[bot[i],top[i]]
然后有m(C(n,2))个限制关系,对于每个限制关系,我们告诉你——
{a[l]+...+a[r]}的和为偶数还是奇数。
让你判定在满足所有限制关系的条件下,{a[]}有多少种可行方案。
输出方案数和满足要求的最小字典序的解。

【类型】
前缀和思想 DP

【分析】
这道题的解决,首先要引入前缀和思想。
就是,我们知道了a[l]+a[r]的奇偶性,其实相应知道的是——sum[r]-sum[l-1]的奇偶性。
于是,我们所抽象出的每个点,都表示一个前缀和。

然后,我们可以预处理出——
如果一个点的状态确定了,哪些点的状态也是确定的。
这个可以通过端点并查集或者简单的暴力bfs(dfs)实现。
我们除了知道,"如果一个点的状态确定了,哪些点的状态也是确定的。"之外,
我们还可以知道,如果一个点的状态确定了,哪些点的状态是奇数;哪些点的状态是偶数。
(当然,如果存在矛盾和冲突,也是可以判定出来的)

这个的实现,我们可以——
枚举这个点是奇数还是偶数,然后就知道搜到其他点的状态是奇数还是偶数了。
如果搜到的点要求为奇数,我们就把这个状态押进[起点为奇数or偶数]里。

本程序的写法是一个比较机缘巧合的精妙写法——
我们使得起点为奇数,
如果搜到的目标点为奇数,那么[起点][1(目标点为奇数)] |= 目标点状态
其实也恰好为[起点][起点为奇数] 记录着哪些目标点为奇数

如果搜到的目标点为偶数,那么[起点][1(目标点为偶数)] |= 目标点状态
这个对称一下,就是——[起点][起点为偶数] 记录着哪些目标点为奇数

这个是我们程序的预处理过程。
(其实还预处理出了,每个位置选择为奇数或偶数的方案数)

=============================================================================

之后我们要展开DP了(其实这个DP,本质也可以称作为搜索)
一开始,我们只有0状态是确定的,这个前缀和显然只能是0,
这个点可能捆绑确定了一些状态。
我们通过vis(哪些点状态是确定的)和sta(这些确定点具体是奇数还是偶数)来表示状态。
其实当然,我们可以用三进制数来表示我们的具体状态(用UL刚好存得下),并没有什么区别。

我们做这个dfs的时候(相比于DP,我还是更愿意称其为dfs),
我们实际是按照依次枚举这41个点的状态展开的。

这41个点,对应着41个前缀和。也对应着40个需要填充的位置。
过程中会出现这么一些状况——
1,该点的状态已经确定了,我们不需要枚举了。
2,该点的状态未确定,我们需要枚举该点的状态。

具体的写法可以是这样子——
在递归搜索的过程中,我们始终用vis 和 sta,记录"哪些节点的状态确定,具体是什么"

然后,对于当前处理的前缀和(我们按照从前向后的顺序枚举,使得所有前缀和的状态都变得确定化)。
如果其状态是确定的,那么我们可以直接算出该位置的选择方案,并进入下层递归
如果其状态是不确定的,那么我们要枚举该位置是奇数还是偶数,
并积累答案贡献,同时传递下在该点位置确定的条件下,新的vis和sta状态。

等等!
这里有一个重要细节。
如果当前点是一个孤立点,就是与其它状态不发生关系,
那对于其搜索树的展开,我们是不分叉的。
因为:之后的状态如果有要求,我们依然可以从合法的前驱状态中获得继承。
之后的状态如果没要求,我们会枚举之后的状态,依然能够得到其合法前驱。

这样子,我们看到,一个节点只有在——
之前状态对其没有要求,其与之后的点产生关联性的时候,会产开分叉的状态搜索。
显然,这样的分叉最多只会产开n/2次。

于是,复杂度控制在O(2^20)以内,我们可以轻松AC。

【时间复杂度&&优化】
O(2 ^ 20)

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐