CSP-S模拟9 最长上升子序列 独特序列 最大GCD 连续子段
DP+思维
T1:给出n,K,和递增序列a1--k,构造1~n的排列,使得a是它的最长上升子序列。(n<=2e5)
容易想到在ai~a(i+1)之间插入<ai的数,在a(k-1) ~ a(k)之间倒序插入>ak的数,。-->10tps
正解:考虑
7 2
3 7
you will make:3 2 1 7 5 4
but is better: 3 1 7 5 4 2
也就是说每ai~ai+1之间只要放1个<ai的数,最后所有剩下的<ak的数只要倒序放进原序列里就行。
为什么倒序?
考虑当a的项都很小,最后剩下的数ak>Min>a1k-1,那么对于任意倒序正过来,都可以和a1k-1构成长度k+1的序列,不合法。
考虑当a的项都很大剩下的数Min<a(x)(x<<k),那么一定保证插在空位里的都有数,同样空位的数+剩下的数任意正序长度>=k+1,不合法,所以必须倒序。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=2e5+100;
int n,K,a[N],num[N],cnt,no[N];
int main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
n=re(),K=re();
_f(i,1,K)a[i]=re(),num[a[i]]=1;
_f(i,1,n)
if(!num[i])no[++cnt]=i;
int now=1;
_f(i,1,K-1)
{
chu("%d ",a[i]);
if(now<=cnt&&no[now]<a[i])
{
chu("%d ",no[now]);++now;
}
}
while(cnt>=now&&no[cnt]>a[K])
{
chu("%d ",no[cnt]);--cnt;
}
chu("%d ",a[K]);
while(cnt>=now)chu("%d ",no[cnt]),--cnt;
return 0;
}
/*
*/
T2:给出长度n的序列,求n的子序列使得取出数的方法唯一。(n<=2e5)
最优子结构:子序列S唯一,当且仅当在前i项出现的S_son唯一。
\(dp[i]:表示前i项,选i的唯一序列个数\)
dp[i]=sigma(dp[j])+(pre[j]==0) (pre[i]<=j<i &&sub[j]>i)
\(O(n^2)\)
树状数组优化,每到一个位置,前缀和查询query(i)-query(pre[i]-1)
,删除这个数上一次出现,为了保证sub[j]>i,其实就是对于所有sub出现的都删掉了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
#define int ll
const int N=2e5+100;
const ll mod=998244353;
int n,a[N],dp[N],pre[N],lst[N],sub[N],low[N];
inline void upd(int&x,int y)
{
x=(ll)((ll)x+(ll)y+mod)%mod;
}
inline int query(int x)
{
int ans=0;
while(x)
{
ans+=low[x];
x-=((x)&(-x));
}
return ans;
}
inline void update(int x,int val)
{
while(x<=n)
{
low[x]+=val;
x+=(x&(-x));
}
}
signed main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
n=re();
_f(i,1,n)
{
a[i]=re();
pre[i]=lst[a[i]];
lst[a[i]]=i;
}
_f(i,1,n)lst[i]=n+1;
f_(i,n,1)
{
sub[i]=lst[a[i]];
lst[a[i]]=i;
}
// _f(i,1,n)chu("dp[%d]:%d\n",i,dp[i]);
int ans=0;
_f(i,1,n)
{
upd(dp[i],query(i-1));
if(!pre[i])upd(dp[i],1);
if(pre[i])//如果前面有重复出现的
{
upd(dp[i],-query(pre[i]-1));
update(pre[i],-dp[pre[i]]);//上个位置就没有贡献了
}
update(i,dp[i]);
// chu("update[%lld]:%lld\n",i,dp[i]);
}
f_(i,n,1)
{
if(sub[i]>n)upd(ans,dp[i]);
}
chu("%lld",ans);
return 0;
}
/*
*/
T3:给出n个数,K,可以进行K次给某个序列里的数+1的操作,求gcd最大值。(n<=2e5)
容易发现K>sum,函数关于K递增。
当K<sum,考虑O(n^logn)求出确定gcd(gcd_now<=gcd<Max)后Calc值。
预处理整除和取模后的值和,然后查询时加减即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=3e5+100;
#define int ll
ll n,K,a[N],Max,Min,should,ans,sum[N],Sum;
inline ll gcd(ll x ,ll y)
{
if(!y)return x;
return gcd(y,x%y);
}
int buc[N],sbuc[N],f[N],g[N];
//f[x]:刚好整除x的数个数
//g[x]:/x剩下的数
inline void calc(int mod)
{
for(int i=mod;i<=Max;i+=mod)
{
int l=i,r=min(Max,l+mod-1);
g[mod]+=(sbuc[r]-sbuc[l-1])*i/mod;
}
}
inline ll Ok(int mod)
{
ll Cnt=0;
Cnt=n*mod-(Sum-g[mod]*mod);
Cnt-=f[mod]*mod;
// chu("%lld cost:%lld\n",mod,Cnt);
return Cnt<=K;
}
signed main()
{
//freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
n=re(),K=re();
Min=1e10;
_f(i,1,n)
{
a[i]=re();buc[a[i]]++;
Sum+=a[i];
Max=max(a[i],Max);
// Min=min(a[i],Min);
}
//尝试都补成Max
should=0;int yes=1;
_f(i,1,n)
{
should+=Max-a[i];
if(should>K)
{
yes=0;break;
}
}
if(yes)
{
ans=(K-should)/n+Max;
chu("%lld",ans);
return 0;
}
Min=a[1];
_f(i,2,n)
{
Min=gcd(Min,a[i]);
}
ans=Min;
_f(i,1,Max)sbuc[i]=sbuc[i-1]+buc[i];
_f(i,1,Max)
{
for(ll j=1;j*i<=Max;++j)
f[i]+=buc[j*i];
}
_f(i,Min+1,Max-1)
calc(i);
//_f(i,Min+1,Max-1)
// chu("g[%d]:%lld\n",i,g[i]);
//chu("%lld",ans);
_f(i,Min+1,Max-1)
{
if(Ok(i))ans=i;
}
chu("%lld",ans);
return 0;
}
/*
5 12345
1 2 3 4 5
3 6
3 4 9
3 4
30 10 20
*/
T4:n序列,K,求最少交换次数,使得n中出现连续1--K。(n<=2e5)
\(dp[i][j]:表示考虑前i个数,已经交换出来的数的状态集合是j的最小交换步数\)
考虑J是已经达到的K集合\(1__5__2__4_3_\)可能是这样,那么代价来自2部分
(1)内部逆序对,在每次加数的时候算出对于x>x的数个数累加(2)交换成连续,把空位补齐,要么已经选择的右移,要么没选择的左移,取一个最小就行。
\(dp[i][j|Si]=min(dp[i-1][j|Si]+move[j|Si],dp[i-1][j]+bit[j>>Si])\)
压缩成一维,需要倒序,因为对于大的状态,我先强制空位,然后考虑加数。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1<<16;
#define INF 0x3f3f3f3f
int dp[N+100],bit[N+100],you[N+100],n;
int main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
n=re();int K=re();
_f(i,1,(1<<K)-1)
{
bit[i]=bit[i>>1]+(i&1);
you[i]=min(you[i],K-you[i]);
you[i]=min(bit[i],K-bit[i]);
//chu("t[%d]:%d\n",i,you[i]);
}
_f(i,1,1<<K)dp[i]=INF;
dp[0]=0;
_f(i,1,n)
{
int a=re()-1;
f_(j,(1<<K)-1,0)
{
if(dp[j]!=INF)
{
if(((1<<a)&j)==0)dp[j|(1<<a)]=min(dp[j|(1<<a)],dp[j]+bit[j>>a]);
dp[j]+=you[j];
}
}
}
chu("%d",dp[(1<<K)-1]);
return 0;
}
/*
*/