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; }
我们考虑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; }