这场的数据范围非常微妙。
注意到初始局面是固定的,这提示我们倒着考虑问题。
考虑两个元素的相对位置关系。称一段前缀被还原了,当且仅当前缀中任意两个数的相对位置关系是保证的。这样每次操作事实上是选KKK个元素出来然后拼接到末尾。可以看出的是,被还原的前缀只会增多不会减少,因此可以直接记入状态。进一步转化,一段前缀[1,l][1,l][1,l]被还原当且仅当对于任意i∈[1,l)i\in [1,l)i∈[1,l),iii和i+1i+1i+1的相对位置关系得到保证。
至此,我们可以清晰的看清楚其中的约束关系:被挪到末尾的元素的集合是确定的,每次挪动时iii和i+1i+1i+1不能同时被挪到末尾。但是这个限制太难处理了,我尝试对连续段贪心但是未果。
考虑将限制放宽,这样我们可以避开贪心直接求解:构造序列{Xi}\{X_i\}{Xi},如果第kkk次操作将iii挪到了末尾,那么Xi←Xi+2kX_i\gets X_i+2^kXi←Xi+2k。记{Qi}\{Q_i\}{Qi}为{Pi}\{P_i\}{Pi}的逆排列,据上面分析我们可以写出{Xi}\{X_i\}{Xi}合法的充要条件:对于任意i∈[1,n)i\in [1,n)i∈[1,n),如果Qi<Qi+1Q_i<Q_{i+1}Qi<Qi+1那么Xi≤Xi+1X_i\le X_{i+1}Xi≤Xi+1,如果Qi>Qi+1Q_i>Q_{i+1}Qi>Qi+1那么Xi<Xi+1X_i<X_{i+1}Xi<Xi+1。这样我们巧妙地把序列问题和二进制勾连起来了。
二进制我熟啊。从高到低位考虑,因为涉及到连续段所以要套一个区间dpdpdp。所以还是要回到区间啊。
复杂度O(n4)O(n^4)O(n4)。
#include<bits/stdc++.h>
#define db double
#define fi first
#define se second
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,K,p[105],pre[105];
ll c[105],dp[105][105][105];
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>K;
for(int i=1;i<=K;i++)cin>>c[i];
for(int i=1;i<=n;i++){
int x;cin>>x,p[x]=i;
}
for(int i=2;i<=n;i++)pre[i]=pre[i-1]+(p[i-1]>p[i]);
memset(dp,0x3f,sizeof dp);
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if(pre[j]-pre[i]==0){
dp[K+1][i][j]=0;
}
}
}
for(int i=K;i>=1;i--){
for(int j=1;j<=n;j++){
for(int k=j;k<=n;k++){
dp[i][j][k]=dp[i+1][j][k];
for(int l=j;l<k;l++){
dp[i][j][k]=min(dp[i][j][k],dp[i+1][j][l]+dp[i+1][l+1][k]+c[i]*(k-l));
}
}
}
}
if(dp[1][1][n]==inf)cout<<-1;
else cout<<dp[1][1][n];
}
难啊。还得是你AGCAGCAGC的题目。。。
看到N,KN,KN,K这么小,考虑直接暴力维护。加入一个元素的本质就是将原集合SSS右移aia_iai位然后取并。经验告诉我们aia_iai应该按从小到大加入,这样连续段的数目最小。但是如果分析不仔细的话可能就直接上暴力维护了,显然KKK这么小就没用上。
很难相信,AGCAGCAGC题目的正解竟然是暴力讨论。记Si=∑j≤iAiS_i=\sum_{j\le i}A_iSi=∑j≤iAi,XiX_iXi表示1∼Si1\sim S_i1∼Si中不能被表示为子集和的数的个数,经验告诉我们可以按从小到大的顺序加数,不妨来细致讨论一下。问题来了,为啥能想到这题直接讨论就行?
1.11.11.1 若Ai>Si−1A_i>S_{i-1}Ai>Si−1,那么(Xi−1∪[Si−1+1,Ai−1])⊆Xi(X_{i-1}\cup [S_{i-1}+1,A_{i}-1])\subseteq X_i(Xi−1∪[Si−1+1,Ai−1])⊆Xi,并且对于v∈[Ai,Si]v\in [A_i,S_i]v∈[Ai,Si],v∈Xi⇔v−Ai∈Xi−1v\in X_i\Leftrightarrow v-A_i\in X_{i-1}v∈Xi⇔v−Ai∈Xi−1,这样的检查是容易的,不妨先假设后面的数对前面没有影响。或者直接看成是区间平移,假装有一个数据结构来维护它。
1.21.21.2 若Ai≤Si−1A_i\le S_{i-1}Ai≤Si−1,那么我们先对[1,Ai−1][1,A_i-1][1,Ai−1]中的数进行检查,如果超过KKK就可以直接结束了。套用前面的观察(区间平移),我们意识到可以将∣Xi∣|X_i|∣Xi∣控制在一定范围内。仍然可以将一个元素是否在XiX_iXi中的充要条件写出:对于v∈[Ai,Si−1]v\in [A_i,S_{i-1}]v∈[Ai,Si−1],v∈Xi⇔v−Ai∈Xi−1v\in X_i\Leftrightarrow v-A_i\in X_{i-1}v∈Xi⇔v−Ai∈Xi−1且v∈Xi−1v\in X_{i-1}v∈Xi−1,对于v∈[Si−1+1,Si]v\in [S_{i-1}+1,S_i]v∈[Si−1+1,Si],v∈Xi⇔v−Ai∈Xi−1v\in X_i\Leftrightarrow v-A_i\in X_{i-1}v∈Xi⇔v−Ai∈Xi−1。显然,我们可以通过遍历Xi−1X_{i-1}Xi−1中的元素从而将XiX_iXi求出。
那么问题来了,∣Xi∣|X_i|∣Xi∣到底有多大?问题的症结出现在寻找的过程中。注意到第二种情况中,当v∈[Ai,Si−1]v\in [A_i,S_{i-1}]v∈[Ai,Si−1]时,这段区间里有值的数不会超过KKK个。当v∈[Si−1+1,Si]v\in [S_{i-1}+1,S_i]v∈[Si−1+1,Si]时有值的数不会超过∣Xi−1∣|X_{i-1}|∣Xi−1∣个,于是∣Xi∣|X_i|∣Xi∣大小呈线性增长,换句话说任意时刻不超过2NK2NK2NK。这已经足够快了。
复杂度O(N2Klog(NK))O(N^2K\log(NK))O(N2Klog(NK))。实际肯定跑的还要快一些。
其实认真分析后发现并不困难,但是当时可能还是把这道题想的太结论了。
#include<bits/stdc++.h>
#define db double
#define fi first
#define se second
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,K;
ll a[65],s[65];
set<ll>X;
void getans(){
assert(X.size()>=K);
for(auto v:X){
cout<<v<<" ";
if(--K==0)exit(0);
}
assert(0);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>K;for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++){
if(a[i]>s[i-1]){
if(X.size()>=K){
getans();
}
for(ll j=s[i-1]+1;j<a[i];j++){
X.insert(j);
if(X.size()>=K){
getans();
}
}
vector<ll>vec;
for(auto v:X){
if(v<=s[i-1]){
vec.pb(v+a[i]);
}
}
for(auto v:vec)X.insert(v);
}
else{
vector<ll>vec,vec2;
for(auto v:X){
if(v<a[i]){
vec.pb(v);
if(vec.size()==K){
for(auto x:vec){
cout<<x<<" ";
}
return 0;
}
}
if(v+a[i]>s[i-1]||X.count(v+a[i])){
vec2.pb(v+a[i]);
}
}
X.clear();
for(auto v:vec)X.insert(v);
for(auto v:vec2)X.insert(v);
}
assert(X.size()<=n*K);
}
if(X.size()<K){
for(ll i=s[n]+1;;i++){
X.insert(i);
if(X.size()==K)break;
}
}
getans();
}
这题是真难。
还是从拆分绝对值入手。可以用代数的手段发现棋子从(x,y)(x,y)(x,y)能走到的点构成了一个矩形边框。同时可以通过二分的手段将棋子能走的范围限制在一个矩形边框内,那么一个棋子下一步合法当且仅当两个矩形边框有交,也就是说DiD_iDi不能超过(x,y)(x,y)(x,y)到矩形边界四个角的最大距离,这样第一步就很好的转化出来了。更关键一步的转化是,如果当前点在二分的矩形边界上,并且Di≤2RD_i\le 2RDi≤2R(其中RRR是二分的答案),那么两个矩形边框总能相交,也就是说这个点总能留在二分的矩形边框上,RRR就一定合法。这同样可以通过严格的代数分析来证明。
那么问题就转化为了从(0,0)(0,0)(0,0)出发能否到达二分的矩形边界上。可以证明只朝一个方向移动是最优的。
但是题目要求还要回到(0,0)(0,0)(0,0),这怎么处理呢?问题的症结在于我们的分析不够细致。考虑将DiD_iDi从小到大排序,可以证明RRR合法应该满足Dn2≤R≤Dn\frac{D_n}{2}\le R\le D_n2Dn≤R≤Dn,下界很好理解,前面已经分析过了;这个上界很有意思,考虑当R=DnR=D_nR=Dn时可以用除DnD_nDn外所有步数走到矩形边界上然后留住,最后恰好走DnD_nDn步回到原点,这样就证完了。这个地方还是比较难想到的。当然顺便可以发现有解的充要条件是∑i<nDi≥Dn\sum_{i<n}D_i\ge D_n∑i<nDi≥Dn,这是容易判断的,因此不再赘述。
有一个非常不符合直观的结论:如果点集{Di}\{D_i\}{Di}能走到边界上的任意一个点,那么一定能走到所有的矩形边界。为什么?这个时候直观法就变得非常难受了。不妨这样来想:将每个DiD_iDi想象成一个向量(xi,yi)(x_i,y_i)(xi,yi),满足∣xi∣+∣yi∣=Di|x_i|+|y_i|=D_i∣xi∣+∣yi∣=Di,考虑从第一象限的(X,Y)(X,Y)(X,Y)调整到(X−1,Y+1)(X-1,Y+1)(X−1,Y+1),只需要存在一个向量满足xiyi>0x_iy_i>0xiyi>0,否则说明只存在朝向二四象限的向量,将这些向量进行调整直到出现朝向xxx轴正方向的向量,这个时候就可以走到(X−1,Y+1)(X-1,Y+1)(X−1,Y+1)了。又因为坐标系是可以旋转的所以能够将所有矩形边界遍历到。这样就把这个非常关键的结论证明到了。
可以证明,最优方案一定经过了矩形边界,这样问题就等价于将{Di}\{D_i\}{Di}分成两个集合,使得两个集合都能到达矩形边界。这就回到我们之前的观察了,只考虑朝一个方向走的情况,设所有<R<R<R的DiD_iDi的和为SSS,如果S≥RS\ge RS≥R那么合法;否则考虑第一个≥R\ge R≥R的数DiD_iDi,如果Di≤S+RD_i\le S+RDi≤S+R那么合法,否则不合法。这个判定的过程要想清楚。比如我就以为只要判集合中最大的数就好了,实际上不是这样的。
因为我们假设了RRR是固定的,所以根据上面的分析我们发现只要保留≥R\ge R≥R的DiD_iDi当中最小和次小的数x,yx,yx,y即可。分两种情况讨论:
1.11.11.1 如果x,yx,yx,y都存在,那么等价于将<R<R<R的DiD_iDi划分成两个集合,一部分≥x−R\ge x-R≥x−R,另一部分≥y−R\ge y-R≥y−R。
1.21.21.2 如果yyy不存在,那么等价于将<R<R<R的DiD_iDi划分成两个集合,一部分≥x−R\ge x-R≥x−R,另一部分≥R\ge R≥R。不难发现就是y=2Ry=2Ry=2R的情况。
可以用bitset\text{bitset}bitset这一工具帮助我们计算。
将RRR从小到大枚举并同时添加物品就可以完成计算了。这样也可以规避掉二分正确性的问题。
复杂度O(n2w)O(\frac{n^2}{w})O(wn2)。
还是太菜了,基本上所有结论都是看的题解。。。
感觉也没用到什么高深的知识点,但是就是理解了很久啊。。。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n;
ll S;
ll d[200005];
bitset<400005>b;
signed main(){
//freopen("data.in","r",stdin);
// freopen("own.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;for(int i=1;i<=n;i++)cin>>d[i];
sort(d+1,d+1+n);
for(int i=1;i<n;i++)S+=d[i];
if(S<d[n]){
cout<< -1;
return 0;
}
S=0;
int j=1;b[0]=1;
for(int i=d[n]/2;i<=d[n];i++){
while(d[j]<i){
b|=(b<<d[j]);
S+=d[j++];
}
int x=d[j]-i,y=(j!=n)?d[j+1]-i:i;
int k=b._Find_next(x-1);
if(k!=b.size()&&S-k>=y){
cout<<i;
return 0;
}
}
}