noip模拟16
A 草莓
真是唐题。原本以为是 dp,没想到是贪心。
然后搓了个 \(n^2\) 的 dp 以为正确性假假的,就优化成了 \(n^3\)。。。
dp 很简单,首先排序,设 \(dp[i][j]\) 表示横着掰到第 \(i\) 个,竖着拜到第 \(j\) 个的最小答案,然后从 \(dp[i-1][j]\) 和 \(dp[i][j-1]\) 分别更新即可。
其实这就是贪心的思路,因为你的 dp 过程无非就是对于两个序列顺序一定时, \(x\) 和 \(y\) 的选取更优策略。那直接把 \(x\) 和 \(y\) 塞到一起排个序就好了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=5e3+3,N=2e5+3;
int x[N],y[N];
int dp[M][M];
int n,m;
int ans=1e18;
inline bool cmp(int a,int b)
{
return a>b;
}
int sumx[N],sumy[N];
signed main()
{
freopen("guiltiness.in","r",stdin);
freopen("guiltiness.out","w",stdout);
cin>>n>>m;
bool _1=1,_2=1,_=1;
int cnt=0;
for(int i=2;i<=n;i++) cin>>x[i];
for(int i=2;i<=m;i++) cin>>y[i];
ans=0;
sort(x+2,x+n+1),sort(y+2,y+m+1);
for(int i=n,j=m;i>1||j>1;)
{
if(x[i]>y[j]) ans+=(m-j+1)*x[i],i--;
else ans+=(n-i+1)*y[j],j--;
}
cout<<ans;
return 0;
}
B 三色
还是神秘 dp。弱化版是紫色。
考虑一个 \(n^3\) 的 dp,设 \(dp[i][j][k]\) 表示现在在 \(i\),即最前面的颜色在 \(i\),第二个颜色在 \(j\),第三个颜色在 \(k\) 的方案数。则通过刷表,可以从 \(dp[i][j][k]\) 分别转移到:
-
\(dp[i+1][j][k]\) 表示 \(i+1\) 放第一种颜色;
-
\(dp[i+1][i][k]\) 表示 \(i+1\) 放第二种颜色;
-
\(dp[i+1][i][j]\) 表示 \(i+1\) 放第三种颜色。
然后因为我们有 \(m\) 个限制,那么就需要对每次转移枚举的 \(i,j,k\) 进行限制。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T;
const int N=504,mod=1e9+7;
int ***dp,*mi1,*mx3,*mi2,*mx2;
inline bool ck(int i,int j,int k)
{
return j<mi1[i]&&k<mi2[i]&&j>=mx2[i]&&k>=mx3[i];
}
namespace q{
int n,m;
void work()
{
cin>>n>>m;
dp=new int **[n+1];
mi1=new int [n+2],mx3=new int [n+2],mi2=new int [n+2],mx2=new int [n+3];
for(int i=0;i<=n;i++)
{
dp[i]=new int *[n+1];
for(int j=0;j<=n;j++)
{
dp[i][j]=new int [n+1];
for(int k=0;k<=n;k++) dp[i][j][k]=0;
}
}
for(int i=0;i<=n;i++)mi1[i]=mi2[i]=0x3f3f3f3f,mx3[i]=mx2[i]=0;
while(m--)
{
int l,r,x;cin>>l>>r>>x;
if(x==1) mi1[r]=min(mi1[r],l);
if(x==2) mi2[r]=min(mi2[r],l),mx2[r]=max(mx2[r],l);
if(x==3) mx3[r]=max(mx3[r],l);
}
dp[0][0][0]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<max(1,i);j++)
{
for(int k=0;k<max(1,j);k++)
{
if(ck(i,j,k))
{
// cout<<i<<" "<<j<<" "<<k<<"\n";
dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%mod;
dp[i+1][i][k]=(dp[i+1][i][k]+dp[i][j][k])%mod;
dp[i+1][i][j]=(dp[i+1][i][j]+dp[i][j][k])%mod;
}
}
}
}
int ans=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<max(i,1);j++)
{
if(ck(n,i,j))ans=(ans+dp[n][i][j])%mod;
}
}
cout<<ans<<"\n";
delete [] mi1;delete []mx3;delete []mi2;delete []mx2;delete []dp;
}
}
signed main()
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--) q::work();
return 0;
}
然后考虑优化。把每一层的 \(i\) 看作一个平面,那么每次转移都是从上一次的一个点,或者一条线的状态转移的。
然后就每次更新这些地方就行。具体可见 这篇博客。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int T,n,m;
namespace q{
int *lsum,*csum,*minj,*maxj,*mink,*maxk,**f;
int *t,lj,*rk,*lk;
inline int sub(int x,int y)
{
return ((x-y)%mod+mod)%mod;
}
inline void del(int j,int k)
{
lsum[j]=sub(lsum[j],f[j][k]);
csum[k]=sub(csum[k],f[j][k]);
f[j][k]=0;
}
void memst(int i)
{
for(int j=lj;j<i;j++)
{
if(j<minj[i]||j>maxj[i])
{
for(int k=0;k<=n;k++) del(j,k);
lk[j]=n,rk[j]=0;
}
else
{
for(int k=lk[j];k<mink[i];k++) del(j,k);
for(int k=rk[j];k>maxk[i];k--) del(j,k);
lk[j]=max(lk[j],mink[i]);
rk[j]=min(rk[j],maxk[i]);
}
}
lj=max(lj,minj[i]);
}
void work()
{
cin>>n>>m;
lsum=new int [n+1],csum=new int [n+1],minj=new int [n+1],maxj=new int [n+1],
mink=new int [n+1],maxk=new int [n+1],t=new int [n+1],lk=new int [n+1],rk=new int [n+1];
f=new int *[n+1];
for(int i=0;i<=n;i++)
{
f[i]=new int [n+1];
for(int j=0;j<=n;j++) f[i][j]=0;
lsum[i]=csum[i]=minj[i]=mink[i]=0;
maxj[i]=maxk[i]=n;
lk[i]=0,rk[i]=n;
}
int l,r,x;
while(m--)
{
cin>>l>>r>>x;
if(x==1) maxj[r]=min(maxj[r],l-1);
if(x==2) minj[r]=max(minj[r],l),maxk[r]=min(maxk[r],l-1);
if(x==3) minj[r]=max(minj[r],l+1),mink[r]=max(mink[r],l);
}
lj=0;
f[0][0]=lsum[0]=csum[0]=1;
for(int i=1;i<=n;i++)
{
if((double)clock()/CLOCKS_PER_SEC>=0.96) return cout<<0,void();
for(int k=0;k<i;k++) t[k]=(lsum[k]+csum[k])%mod;
if(minj[i]<=i-1&&maxj[i]>=i-1)
{
for(int k=mink[i];k<=min(maxk[i],max(0,i-2));k++)
{
f[i-1][k]=(f[i-1][k]+t[k])%mod;
csum[k]=(csum[k]+t[k])%mod;
lsum[i-1]=(lsum[i-1]+t[k])%mod;
}
}
memst(i);
}
int ans=0;
for(int i=0;i<=n;i++) ans=(ans+lsum[i])%mod;
cout<<ans<<"\n";
delete []f,delete []lsum,delete []csum,delete []minj,delete []mink,delete []maxj,delete []maxk,delete []lk,delete []rk,delete []t;
}
}
signed main()
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)q::work();
return 0;
}
C 博弈
根据题意,这三个数一定是向着减小差距的方向修改的。
考虑最简单的、对于一个三元组的 check。你会发现如果他们三个都不同的话,一定能赢。否则,如果三个数相等,那第一把就输了。那么 \(_i\) 互不相同的方案数就是 \(\binom{n}{3}\)。
看来剩下的只剩下形如 \(a,a,b\) 这种形式的了。还是考虑多少次能够到达修改的临界点,发现当 \(\text{lowbit}(a-b)\) 的位数是偶数个才能满足。
双 \(\log\):对每一位开一个 map,对于所有 \(a_i\) 枚举 \(b\);
单 \(\log\):建一棵从下往上的 Trie 树。目前正在卡常。
D 后缀数组
\(60\) 分做法:
首先用 FHQ 维护那 \(m\) 个修改操作,时间复杂度 \(m\log n\)。实现是简单的,具体可以参考文艺平衡树。
然后,统计所有 \(rk[a[i]+1]<rk[a[i+1]+1]\) 的数量,答案是 \(2^k\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+4,mod=998244353;
int a[N];
int acnt;
struct TREE
{
struct NODE
{
int l,r,val,siz,key;
bool lz;
}tree[N<<1];
int cnt{},root{};
inline int newnode(int val)
{
tree[++cnt].val = val,tree[cnt].siz = 1,tree[cnt].key = rand();
return cnt;
}
inline void ins(int val){root = merge(root,newnode(val));}
inline void pushup(int x){tree[x].siz = tree[tree[x].l].siz + tree[tree[x].r].siz + 1;}
inline void pushdown(int x)
{
if(tree[x].lz)
{
tree[x].l ^= tree[x].r ^= tree[x].l ^= tree[x].r;
tree[tree[x].l].lz ^= 1;tree[tree[x].r].lz ^= 1;
tree[x].lz = false;
}
}
inline void split(int x,int siz, int &l,int &r)
{
if(!x) l = r = 0;
else
{
pushdown(x);
if(tree[tree[x].l].siz < siz){ l = x;split(tree[x].r,siz-tree[tree[x].l].siz-1,tree[x].r,r);}
else{r = x;split(tree[x].l,siz,l,tree[x].l);}
pushup(x);
}
}
int x,y,z;
inline int merge(int x,int y)
{
if(!x || !y) return x + y;
if(tree[x].key > tree[y].key)
{
pushdown(x);tree[x].r = merge(tree[x].r,y);
pushup(x);return x;
}
else
{
pushdown(y);tree[y].l = merge(x,tree[y].l);
pushup(y);return y;
}
}
inline void reverse(int l,int r)
{
split(root,l-1,x,y);
split(y,r-l+1,y,z);
tree[y].lz ^= 1;
root = merge(merge(x,y),z);
}
inline void work(int l,int r)
{
split(root,l-1,x,y);
split(y,r-l+1,y,z);
root = merge(y,merge(x,z));
}
inline void output(int x)
{
if(!x) return;
pushdown(x);
output(tree[x].l);
a[++acnt] = tree[x].val;
output(tree[x].r);
}
}tr;
int rk[N],dp[N];
signed main()
{
freopen("sa.in","r",stdin);
freopen("sa.out","w",stdout);
srand(time(NULL));
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) tr.ins(i);
while(m--)
{
int op,u,v;cin>>op>>u>>v;
if(op==0) tr.work(u,v);
else tr.reverse(u,v);
}
tr.output(tr.root);
for(int i=1;i<=n;i++)rk[a[i]]=i;
dp[1]=1;
for(int i=2;i<=n;i++)
{
if(rk[a[i]+1]>rk[a[i-1]+1])
dp[i]=dp[i-1]*2%mod;
else dp[i]=dp[i-1];
}
cout<<dp[n];
}