\(noip模擬30\;solutions\)
所以說,這次被初中的大神給爆了?????
其實真的不甘心,這次考場上的遺憾太多,浪費的時間過多,心情非常不好
用這篇題解來結束這場讓人傷心的考試吧
\(T1\;毛一探\)
其實這個題我本來是考場上就能AC的
不得不說這個\(meet \;in\;the\;middle\)思想真的沒誰了。
我在考場上一分鐘想出來如何用一個複雜度不確定的辦法來搞定他
(這個複雜度最劣是\(\mathcal{O(2^{n+1})}\),但是資料比較善良,給了我75pts,但是其實我這個方法就是正解,就是折半的思想)
想完之後我就覺得有點不太好,就開始優化我的暴力,\(1h20min\;later\),我醒悟了
發現自己根本優化不了,所以打了個我的演算法,又打了一個更暴力的暴力,拍了拍沒錯。。。
我的暴力是根據兩個人的分值的和相同來搞得,用了map和vector,儲存能夠達到每個可能的和的方案(也就是一個20位的二進位制數)
每次找到一個和,就遍歷相同的和的其他方案,如果沒有重複的位,那麼就標記,最後統計答案
75pts(暴力)
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define pa pair<int,int>
#define mpa(x,y) make_pair(x,y)
#define fi first
#define se second
const int N=25;
int n,m[N];
int dp[1<<20],ans,cnt;
bool vis[1<<20];
unordered_map<int,int> mp;
vector<int> vec[1<<20];
ll jc[N];
ll C(int x,int y){
return jc[x]/jc[y]/jc[x-y];
}
signed main(){
int a=scanf("%d",&n);
bool flag=0;
for(re i=1;i<=n;i++){
a=scanf("%d",&m[i]);
if(m[i]!=m[i-1]&&i!=1)flag=true;
}
/*if(!flag){
jc[0]=1;for(re i=1;i<=n;i++)jc[i]=jc[i-1]*i;
for(re i=2;i<=n;i+=2)ans+=C(n,i);
printf("%d",ans);
return 0;
}*/
dp[0]=0;
for(re i=1;i<=n;i++)dp[1<<(i-1)]=m[i];
for(re i=1;i<(1<<n);i++){
dp[i]=dp[i-(i&(-i))]+dp[i&(-i)];
if(mp.find(dp[i])==mp.end())
mp.insert(mpa(dp[i],++cnt));
int who=mp.find(dp[i])->se;
for(re j=0;j<vec[who].size();j++){
if(i&vec[who][j])continue;
vis[i|vec[who][j]]=true;
}
vec[who].push_back(i);
}
for(re i=1;i<(1<<n);i++)
if(vis[i])ans++;
printf("%d",ans);
return 0;
}
所以正解其實跟我的列舉方式差不多,不過是把原序列拆成兩部分,
這一次我們無法列舉兩個人分別的和,所以此時列舉狀態的複雜度是\(\mathcal{O(3^{\frac{n}{2}})}\)
因為一道題可以不選,第一個人,第二個人。。
那我們統計答案的時候就需要利用兩側的選出來的兩個人的元素之和的差值,
因為此時只要兩測的兩個人的差值相同,那麼就會產生一個合法的答案
還是利用map和vector,只不過這次存的是兩側的差值,
還是最後直接統計答案,雖然我不會手寫hash表,但是我跑的飛快
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define mpa(x,y) make_pair(x,y)
#define se second
#define fi first
const int N=25;
int n,n1,n2,w[N];
map<int,int> mp;
vector<int> vec[1<<20];
int cnt,ans;
bool vis[1<<20];
void dfs1(int now,int mx,int sum1,int sum2,int s){
if(now==mx+1){
int tmp=abs(sum1-sum2);
if(mp.find(tmp)==mp.end())
mp.insert(mpa(tmp,++cnt));
int who=mp.find(tmp)->se;
vec[who].push_back(s);
return ;
}
dfs1(now+1,mx,sum1+w[now],sum2,s|(1<<now-1));
dfs1(now+1,mx,sum1,sum2+w[now],s|(1<<now-1));
dfs1(now+1,mx,sum1,sum2,s);
}
void dfs2(int now,int mx,int sum1,int sum2,int s){
if(now==mx+1){
int tmp=abs(sum1-sum2);
if(mp.find(tmp)==mp.end())return ;
int who=mp.find(tmp)->se;
for(re i=0;i<vec[who].size();i++)
vis[s<<n1|vec[who][i]]=true;
return ;
}
dfs2(now+1,mx,sum1+w[now+n1],sum2,s|(1<<now-1));
dfs2(now+1,mx,sum1,sum2+w[now+n1],s|(1<<now-1));
dfs2(now+1,mx,sum1,sum2,s);
}
signed main(){
scanf("%d",&n);
n1=n/2;n2=n-n1;
for(re i=1;i<=n;i++)scanf("%d",&w[i]);
dfs1(1,n1,0,0,0);
dfs2(1,n2,0,0,0);
for(re i=1;i<(1<<n);i++)
if(vis[i])ans++;
printf("%d",ans);
}
說實話,這麼多map的用法都是之前看一個std看到的
\(T2\;,毛二探\)
哈哈哈,這個題是我頭一次想到dp題的一點點思路(考場上的時候)
然後考完試改這個題的時候只改了半個小時就過了,不得不說字首和優化也是個好東西
就你先根據每個數的移動情況判斷一下每個移動的優先順序順序
要是有一個順序被賦值成兩個不同的優先順序,那麼就直接輸出零,因為一個排列不可能有兩種優先順序
如果知道了這些,那轉移方程也就極其的好想了。
設\(f[i][j]\)表示我們目前處理q(就是那個優美序列,用來規劃轉移的)的第i個數,放在第j個位置
那麼轉移就是直接從上一層轉移,如果當前交換需要比上一個早的話,那就插在上一個位置的前面,如果晚就放在後面。。。。這不用我說也知道吧
所以有了我們當前的\(\mathcal{O(n^3)}\)的轉移,可以拿到70pts
O(n^3)暴力轉移
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=5005;
const ll mod=1e9+7;
int n,w[N],mov[N];
int typ[N];
ll f[N][N],ans;
signed main(){
scanf("%d",&n);
for(re i=1;i<=n;i++){
scanf("%d",&w[i]);
w[i]++;
}
for(re i=1;i<=n;i++)
mov[w[i]]=i-w[i];
int flag=0;
for(re i=1;i<=n;i++){
if(mov[i]==0)flag=1;
if(mov[i]>0){
for(re j=i+1;j<i+mov[i];j++){
if(typ[j])flag=1;
typ[j]=2;
}
}
if(mov[i]<0){
for(re j=i-1;j>i+mov[i];j--){
if(typ[j])flag=1;
typ[j]=1;
}
}
}
if(flag){
printf("0");
return 0;
}
f[1][1]=1;
for(re i=2;i<n;i++){
for(re j=1;j<=i-1;j++){
if(typ[i]==1){
for(re k=1;k<=j;k++)
f[i][k]=(f[i][k]+f[i-1][j])%mod;
}
else{
for(re k=j+1;k<=i;k++)
f[i][k]=(f[i][k]+f[i-1][j])%mod;
}
}
}
for(re i=1;i<n;i++)ans=(ans+f[n-1][i])%mod;
printf("%lld",ans);
}
所以你打完這個暴力轉移之後,你發現每一個狀態都是從前一個的狀態的一個連續區間轉移來的
所以就可以直接字首和優化,\(\mathcal{O(1)}\)轉移了
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=5005;
const ll mod=1e9+7;
int n,w[N],mov[N];
int typ[N];
ll f[N][N],ans,sum[N][N];
signed main(){
scanf("%d",&n);
for(re i=1;i<=n;i++){
scanf("%d",&w[i]);
w[i]++;
}
for(re i=1;i<=n;i++)
mov[w[i]]=i-w[i];
int flag=0;
for(re i=1;i<=n;i++){
if(mov[i]==0)flag=1;
if(mov[i]>0){
for(re j=i+1;j<i+mov[i];j++){
if(typ[j])flag=1;
typ[j]=2;
}
}
if(mov[i]<0){
for(re j=i-1;j>i+mov[i];j--){
if(typ[j])flag=1;
typ[j]=1;
}
}
}
if(flag){
printf("0");
return 0;
}
f[1][1]=1;
sum[1][1]=1;
for(re i=2;i<n;i++){
for(re j=1;j<=i-1;j++){
if(typ[i]==1){
f[i][j]=(f[i][j]+sum[i-1][i-1]+mod-sum[i-1][j-1])%mod;
}
else{
f[i][j+1]=(f[i][j+1]+sum[i-1][j]+mod)%mod;
}
}
for(re j=1;j<=i;j++){
sum[i][j]=(sum[i][j-1]+f[i][j])%mod;
}
}
for(re i=1;i<n;i++)ans=(ans+f[n-1][i])%mod;
printf("%lld",ans);
}
\(T3\;毛三探\)
這個題就非常神奇的卡過了
直接列舉x,二分此時x能達到的最大重量的最小值,因為必須連續,判斷直接\(\mathcal{O(n)}\)
如果只是這樣那你就過不了了,
注意判斷一下之前的x中的最有答案能不能合法,合法再去二分,不合法直接進行下一個x
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=10005;
int n,p,k,w[N],no[10005];
int ans=0x3f3f3f3f,sum;
bool check(int x){
int j=1;
for(re i=1;i<=k;i++){
int now=0;
for(;j<=n;j++){
if(now+no[j]>x)break;
now+=no[j];
}
if(j==n+1)return true;
}
return false;
}
signed main(){
scanf("%d%d%d",&n,&p,&k);
for(re i=1;i<=n;i++)scanf("%d",&w[i]),sum+=w[i];
for(re x=0;x<p;x++){
int l=0,r=sum,mid;
for(re i=1;i<=n;i++)no[i]=(w[i]+x)%p;
if(!check(ans))continue;
while(l<r){
mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
ans=min(ans,r);
}
printf("%d",ans);
}