1. 状态压缩DP:对于一个集合,把每一个元素选择与否的情况对应到一个二进制位里,从而把集合的状态压缩成一个整数,方便计算与维护。
POJ2441 Arrange the Bulls:有n头牛和m兼屋子,每头牛都只喜欢住在某几间屋子里,问所有牛都满意的方案有多少种。
屋子的居住一共有1<<m种情况,dp[i]表示状态为i时一共有多少种方案。遍历每一头牛,在每一种方案下看每一间屋子是否能够居住。比状态i多出第k间屋子的状态是i|(1<<k),最后将所有状态中有n个1 的状态总数加起来即可。
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 21
#define INF 0x3f3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n, m, t, x;
int maze[maxn][maxn];
int dp[1 << maxn];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
scanf("%d", &t);
for (int j = 0; j < t; j++)
{
scanf("%d", &x);
maze[i][x - 1] = 1;
}
}
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int j=0;j<n;j++)
{
for (int i = (1<<m)-1; i >= 0; i--)
{
if (dp[i] == 0)continue;
for (int k = 0; k < m; k++)
{
if (!((i >> k) & 1))
{
if (maze[j][k])
dp[i | (1 << k)] += dp[i];
}
}
dp[i] = 0;
}
}
int ans = 0;
for (int i = 0; i < (1 << m); i++)
ans += dp[i];
printf("%d\n", ans);
system("pause");
return 0;
}
2.带权并查集:对于每个节点保存其一个权值来表示节点与其根节点之间的某种关系,在查询与合并时更新权值。由于权值是由节点指向其根节点,在合并分析中采用向量的思想来处理。
POJ1988 Cube Stacking:有若干个木块,要求支持两种操作:将a所处的一列木块放到b的上面,查询a所在的一列中a的下面有多少个木块。
此题需要保存两个权值:sum[i]表示i所处的一列中总的木块数目,num[i]表示i的下面的木块数目。在查询根节点时有num[i]+=num[pre[i]],合并时有num[i]=sum[pre[i]],sum[i]+=sum[pre[i]]。实际上,对于i的每个祖先t,num[t]所代表的都是t移过来时所在的那一列的总数,只有叶子节点是移过来时那一列中它下面的数目。在查询的过程中依次更新成下方所有的数目之和。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 30050
#define INF 0x3f3f3f3f
#define eps 1e-8
const int mod = 1e9 + 7;
using namespace std;
typedef long long ll;
int n, x, y;
char s[10];
int pre[maxn], sum[maxn], num[maxn];
int finding(int x)
{
if (x == pre[x])return x;
int tmp = finding(pre[x]);
num[x] += num[pre[x]];
return pre[x] = tmp;
}
int main()
{
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i < maxn; i++)
pre[i] = i, sum[i] = 1, num[i] = 0;
for (int i = 0; i < n; i++)
{
scanf("%s", s);
if (s[0] == 'M')
{
scanf("%d%d", &x, &y);
int fx = finding(x);
int fy = finding(y);
if (fx != fy)
{
pre[fx] = fy;
num[fx] = sum[fy];
sum[fy] += sum[fx];
}
}
else
{
scanf("%d", &x);
int fx = finding(x);
printf("%d\n", num[x]);
}
}
}
return 0;
}
HDU3038 How Many Answers are Wrong:对于某个数列,给出若干个区间和,判断有多少个区间和是与先前矛盾的。
这是对区间操作,val[i]表示i与其根节点之间的距离。合并时:若fa==fb,有:
从向量图可以看出,如果val[b]-bal[a]!=x,这条结果就不合法。
如果fa!=fb,就有:
从图可知应令pre[fb]=fa且val[fb]=val[a]+x-val[b]。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 200050
#define INF 0x3f3f3f3f
#define eps 1e-8
const int mod = 1e9 + 7;
using namespace std;
typedef long long ll;
int n, m, a, b, x;
int pre[maxn], val[maxn];
int finding(int x)
{
if (x == pre[x])return x;
int tmp = finding(pre[x]);
val[x] += val[pre[x]];
return pre[x] = tmp;
}
int main()
{
while (scanf("%d%d", &n, &m) != EOF)
{
for (int i = 0; i <= n; i++)pre[i] = i;
memset(val, 0, sizeof(val));
int ans = 0;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &a, &b, &x);
a--;
int fa = finding(a);
int fb = finding(b);
if (fa == fb)
{
if (val[b] - val[a] != x)
ans++;
}
else
{
val[fb] = val[a] + x - val[b];
pre[fb] = fa;
}
}
printf("%d\n", ans);
}
return 0;
}
3.数位dp计数:洛谷1239:求出1-n中0-9各出现多少次。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 15
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
int n,m,len,num;
int dp[maxn][maxn][maxn];
int sum[maxn][maxn];
int ans[maxn],dig[maxn];
void solve(int u,int cnt,int tmp)
{
if(cnt==1)
{
for(int i=0;i<dig[cnt];i++)
ans[i]++;
ans[dig[cnt]]++;
if(len==1)ans[0]--;
}
else
{
for(int i=0;i<dig[cnt];i++)
{
for(int j=0;j<=9;j++)
ans[j]+=dp[cnt][i][j];
}
if(cnt==len&&u)
ans[0]+=(sum[cnt-1][0]-dp[cnt][0][0]);
ans[dig[cnt]]+=(u-tmp*dig[cnt]+1);
solve(u-tmp*dig[cnt],cnt-1,tmp/10);
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<=9;i++)
{
dp[1][i][i]=1;
sum[1][i]=1;
}
num=1;
int t=n,tmp=1;
while(t)
{
dig[++len]=t%10;
t/=10;
tmp*=10;
}
tmp/=10;
for(int i=2;i<=len;i++)
{
num*=10;
for(int j=0;j<=9;j++)
{
for(int p=0;p<=9;p++)
{
if(j==p)dp[i][j][p]+=num;
for(int q=0;q<=9;q++)
dp[i][j][p]+=dp[i-1][q][p];
sum[i][p]+=dp[i][j][p];
}
}
for(int j=0;j<=9;j++)
sum[i][j]+=(sum[i-1][j]-dp[i][0][j]);
}
for(int i=1;i<=len+1;i++)sum[i][0]--;
solve(n,len,tmp);
for(int i=0;i<=9;i++)
printf("%d\n",ans[i]);
return 0;
}