20171122校内训练

各种特判啊。

首先,我们可以发现,每个人看到的帽子种类数要么是帽子总种类数,要么是这个数-1。

所以,如果q[i]有大于两种或两种q[i]的差大于1,那么肯定无解。

我们不妨记比较小的那个q[i]为num1,出现次数为cnt1,比较大的那个数q[i]为num2,出现次数为cnt2

若只有出现一种数,则num2=cnt2=0。

只出现一种数:首先,num1如果=n-1,有解(n顶帽子颜色互异)。我们发现,如果出现的帽子颜色每种至少有两个,那么也可以满足题意,但是此时num1最大只能是n/2。

若出现两种数:如果大的那种数出现次数为一,肯定无解。然后q是小的那个数的人的帽子颜色必须互异且q是大的那个数的人的帽子颜色每种至少有两个。

q是小的那个数的人的帽子颜色必须互异:这里对答案的贡献为cnt1-1(在q是小的那个数的人的视角(他自己头上的帽子颜色不算))

q是大的那个数的人的帽子颜色每种至少有两个:这里对答案的贡献最小为1,最大为cnt2/2。

所以num1必须介于cnt1-1+1与cnt1-1+cnt2/2之间才有解

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    freopen("water.in","r",stdin);freopen("water.out","w",stdout);
    int T;scanf("%d",&T);
    while(T--)
    {
        int n,cnt1=0,cnt2=0,num1=0,num2=0;
        scanf("%d",&n);bool ok=0;
        for(int i=1;i<=n;i++)
        {
            int a;scanf("%d",&a);
            if(a==num1)cnt1++;
            else if(a==num2)cnt2++;
            else if(num1==0)num1=a,cnt1++;
            else if(num2==0)num2=a,cnt2++;
            else ok=1;
        }
        if(ok){puts("No");continue;}
        if(num1>num2&&num2!=0)swap(cnt1,cnt2),swap(num1,num2);
        if(num2-num1>=2){puts("No");continue;}
        if(num2==0)
        {
            if(num1==n-1){puts("Yes");continue;}
            if(num1<=n/2){puts("Yes");continue;}
            puts("No");continue;
        }
        else
        {
            if(cnt2==1){puts("No");continue;}
            if(num1>=cnt1&&num1<=cnt1+cnt2/2-1){puts("Yes");continue;}
            puts("No");continue;
        }
    }
    return 0;
}
View Code

 

我们考虑DP

首先我们可以发现,每个涂色方案满不满足条件,主要看的是红,绿,蓝三种颜色的最后一次出现位置。所以我们的状态可以设成这样:dp[i][j][k][l]表示前i个数(即询问确定到i(询问不考虑r>i的区间)),红色最后一次出现的位置j,绿色最后一次出现的位置k,蓝色最后一次出现的位置l。

但这样复杂度显然是O(n^4)的,考虑优化。

我们可以发现,一定有一种颜色的最后出现位置为i,且询问对颜色种类数没有做过多的要求(即只要有这么多种就好了,而不是这里面具体出现了哪几种颜色),所以我们考虑这么表示状态:f[i][j][k]表示确定到i,三种颜色最后一次出现的位置分别是i,j,k(j>k),显然i,j,k互不相等。但是,我们规定,dp[i][j][0]表示三种颜色其中两种颜色最后一次出现的位置分别是i,j,没有出现过第三种颜色。dp[i][0][0]表示只有出现一种颜色。

转移的话呢,显然,我们判断dp[i][j][k]是由什么东西转移来的显然不太好判断(至少时间复杂度高),这时候我们就思考,dp[i][j][k]可以转移出什么。把黄圈看成蓝圈。红圈表示红色最后一次出现位置,绿圈表示绿色最后一次出现位置,黄圈表示蓝色最后一次出现位置。然后第i+1个位置有可能填红绿黄三种颜色之一,即后三幅图的状态都可以从第一幅图转移过来。于是有如下状态转移方程:

dp[i-1][j][k](j是绿圈位置,k是红圈位置,i-1是黄圈位置)

dp[i][j][k](j是绿圈位置,k是红圈位置,i是黄圈位置)

dp[i][i-1][k](i-1是黄圈位置,k是红圈位置,i是绿圈位置)

 dp[i][i-1][j](i-1是黄圈位置,j是绿圈位置,i是红圈位置)

但是我们还没判这些状态合不合法呢。我们看看所有i==询问的右端点的询问,对于每种状态dp[i][j][k],看一下这个区间中是不是恰好有那么多种数,如果不是,dp[i][j][k]=0。

初始化dp[1][0][0]=3。特殊的转移dp[i-1][0][0]可以转移到dp[i][0][0],dp[i][i-1][0],dp[i][i-1][0](这里没有多写一遍,就是可以转移到两次dp[i][i-1][0])

时间复杂度O(n^3),但是,由于这题空间复杂度8MB,所以我们把i这维滚动

#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
const int mod=1e9+7;
int dp[2][301][301];
typedef pair<int,int> P;
vector<P> R[301];
int main()
{
    int n,m;scanf("%d%d",&n,&m);int ok=0;
    for(int i=1;i<=m;i++){int l,r,x;scanf("%d%d%d",&l,&r,&x);R[r].push_back(P(l,x));if(l==r&&x>1)ok=1;}
    if(ok){puts("0");return 0;}
    dp[1][0][0]=3;
    for(int i=2;i<=n;i++)
    {
        int o=i&1;
        for(int j=0;j<i;j++)for(int k=0;k<j;k++)dp[o][j][k]=0;
        dp[o][0][0]=0;
        dp[o][0][0]=(dp[o][0][0]+dp[o^1][0][0])%mod;
        dp[o][i-1][0]=(dp[o][i-1][0]+dp[o^1][0][0])%mod;
        dp[o][i-1][0]=(dp[o][i-1][0]+dp[o^1][0][0])%mod;
        for(int j=0;j<i;j++)
        for(int k=0;k<j;k++)
        {
            dp[o][j][k]=(dp[o][j][k]+dp[o^1][j][k])%mod;
            dp[o][i-1][j]=(dp[o][i-1][j]+dp[o^1][j][k])%mod;
            dp[o][i-1][k]=(dp[o][i-1][k]+dp[o^1][j][k])%mod;
        }
        for(int l=0;l<R[i].size();l++)
        {
            for(int j=0;j<i;j++)
            for(int k=0;k<j;k++)
            {
                int ok1=0;if(j>=R[i][l].first)ok1=1;
                int ok2=0;if(k>=R[i][l].first)ok2=1;
                if(ok1+ok2+1!=R[i][l].second)dp[o][j][k]=0;
            }
            if(R[i][l].second!=1)dp[o][0][0]=0; 
        }
    }
    int ans=0;
    for(int j=0;j<n;j++)
    for(int k=0;k<j;k++)ans=(ans+dp[n&1][j][k])%mod;
    printf("%d",(ans+dp[n&1][0][0])%mod);
    return 0;
}
View Code

 

posted @ 2017-11-23 22:01  lher  阅读(127)  评论(0编辑  收藏  举报