SPOJ 423 Assignments 状态DP

这个题目搁置了这么久,终于搞完了。

给n个人分配n个课程,已经告诉了你n个人对哪几门感兴趣,问最多有多少种分配方式

我刚开始都没找到这怎么还可以状态dp,哪来的状态转移,想用暴力DFS,果断TLE的妥妥的。

后来给殷犇发了这个题目,他还说你刷个这水题还刷得这包子劲,这题目就是后一行的状态由前一行得到,枚举当前这一行分配的状态,如果可行,就从后面的状态加过来

由于状态只是从上一行转移过来,所以可以用滚动数组,用p表示当前,则!p就为上一行的,每次结束再把p置反即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
int n;
int sta[22];
ll dp[2][1<<21];
int calc[22][1<<21],cnt[22];
void init()
{
    memset(calc,0,sizeof calc);
    memset(cnt,0,sizeof cnt);
    for (int i=0;i<(1<<21);i++) //这里把只有一门课 两门。。。N门课全部用一个数组存起来,calc[i][]就代表有i个课程的状态,因为不止一种 所以开了两维。
    {
        int tmp=0;
        for (int j=i;j;j>>=1)
        {
            if (j&1)
                tmp++;
        }
        calc[tmp][cnt[tmp]++]=i;
    }
}
int main()
{
    int t,a;
    scanf("%d",&t);
    init();
    while (t--)
    {
      scanf("%d",&n);
      for (int i=0;i<n;i++)
      {
          sta[i]=0;
          for (int j=0;j<n;j++)
          {
              scanf("%d",&a);
              if (a==1)
              {
                  sta[i]+=1<<j;//存入学生感兴趣的课程的状态。
              }
          }
      }

      int p=0,all=(1<<n)-1;
      //memset(dp,0,sizeof dp);
      dp[1][0]=1;
      for (int i=0;i<n;i++)//从第0个人循环到第n-1个人
      {
          for (int k=0,s=calc[i+1][k];k<cnt[i+1]&&calc[i+1][k]<=all;s=calc[i+1][++k])//当前是第i号人 则状态必定是calc[i+1][]的状态,比如第三个人就只分配三个课程即可,一旦可行 就从分配了两个课程的状态那里转移过来。
          {
              dp[p][s]=0;
              for (int j=0;j<n;j++)//逐个试探第j号课程是否可以
              {
                  //dp[p][k]=0;
                 if (((1<<j)&sta[i])==0) continue;
                  if ((1<<j)&s)
                  {
                      //dp[p][k]+=1;

                       dp[p][s]+=dp[p^1][(1<<j)^s];//一旦该状态可以,则从上一次的不含新分配的课程的那里转移过来
                  }

                 //else
                  //  dp[p][k]+=dp[p^1][k];
              }
          }
          p^=1;
      }
      printf("%lld
",dp[p^1][all]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/kkrisen/p/3604699.html