CSP-S模拟7
下发文件和题解
A. 序列问题
令状态 f[i] 表示处理 1~i 这些数,并且处理后的序列以 ai 结尾的最优答案.
重要:只有 ai ≤ i 时 f[i] 才合法.
那么一个数 j 想要转移到 i,其必须要满足:
A. j < i;
B. aj < ai;
C. ai - aj ≤ i − j.
这是什么?
三维偏序!
然后写一个 CDQ 分治就有了 80 分的好成绩.
可以发现,满足性质 B、C 一定满足性质 A,且 ai - aj ≤ i − j 可以转化为 j - aj ≤ i − ai,那么可以把这个序列按照 a 值排序(性质 B).
然后用 bit 求一个 i - ai(注意这里的 i 是当前的 ai 在原序列中的位置)的最长不下降子序列(性质 C)的长度即是答案.
点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define rll rg ll
#define maxn 500001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rll x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
template<typename T,size_t siz>
class bit
{
private:
#define lowbit(x) (x&-x)
ll c[siz];
public:
inline void add(ll x,ll n,ll v) { for(rll i=x;i<=n;i+=lowbit(i)) c[i]=max(c[i],v); }
inline ll query(ll x) { rll ans=0;for(rll i=x;i;i-=lowbit(i)) ans=max(ans,c[i]);return ans; }
#undef lowbit
};
struct node
{
ll a,id;
inline friend bool operator<(rg node a,rg node b)
{
if(a.a==b.a) return a.id>b.id;
return a.a<b.a;
}
}a[maxn];
ll n,ans;
ll f;
bit<ll,maxn> t;
int main()
{
n=read();for(rll i=1;i<=n;i++) a[i].a=read(),a[i].id=i;
sort(a+1,a+n+1);
for(rll i=1;i<=n;i++) a[i].a=a[i].id-a[i].a;
for(rll i=1;i<=n;i++)
{
if(a[i].a<0) continue;// 前面说的,不合法情况
f=t.query(a[i].a+1)+1;
ans=max(ans,f);t.add(a[i].a+1,n,f);
}
write(ans);
return 0;
}
B. 钱仓
由于每个仓的 ci 之和等于 n,而且费用和运输距离是成平方增长的关系. 不难想到贪心法.
枚举每一个点为起始点,如果 ci 大于 1 就把多余的一直往后放,只要能放就放,每一个点放完就不能再放了(因为最多只能运一次).
那么代码就写出来了( O(n2) ):
点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define rll rg ll
#define maxn 200001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rg long long x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
ll n,mx,ls;
long long ans=LLONG_MAX;
ll b[maxn],c[maxn];
bool fl[maxn];
static inline long long sol(rll x)
{
rg long long ans=0;memset(fl,0,sizeof(fl));memcpy(b,c,sizeof(c));
for(rll i=x;i<=n;i++)
{
ls=i+1;
while(b[i]>1)
{
while(fl[ls]) ls++;ans+=(long long)(ls-i)*(ls-i);
if(ls<=n) b[i]--,b[ls]++,fl[ls]=fl[ls+n]=1;
else b[i]--,b[ls-n]++,fl[ls-n]=fl[ls]=1;
}
}
for(rll p=1;p<=2;p++)// 这里是赛时害怕丢掉情况加的,其实不需要加.
for(rll i=1;i<=n;i++)
{
ls=i+1;
while(b[i]>1)
{
while(fl[ls]) ls++;ans+=(long long)(ls-i)*(ls-i);
if(ls<=n) b[i]--,b[ls]++,fl[ls]=fl[ls+n]=1;
else b[i]--,b[ls-n]++,fl[ls-n]=fl[ls]=1;
}
}
return ans;
}
int main()
{
n=read();
for(rll i=1;i<=n;i++) c[i]=c[n+i]=read();
for(rll i=1;i<=n;i++) if(c[i]>1) ans=min(ans,sol(i));
write(ans);
return 0;
}
这样就有了 64 分.
如何优化:
那当然是寻找起始点啦!
累加 ci 的前缀和,找到前缀和比这一段数的数量小的,将起始点设置为这个点,同时前缀和归零,不断重复该操作直至第 n 个点. 具体细节请看代码.
这样时间复杂度就变成 O(n) 了.
点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define rll rg ll
#define maxn 200001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
// 这部分和上面的一样,就折叠处理了
static inline ll read() { rll f=0,x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();return f?-x:x; }
static inline void write(rg long long x) { if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10|'0'); }
ll n,mx,ls;long long ans=LLONG_MAX;ll b[maxn],c[maxn];ll sum[maxn],st=1;bool fl[maxn];
static inline long long sol(rll x)
{
rg long long ans=0;memset(fl,0,sizeof(fl));memcpy(b,c,sizeof(c));for(rll i=x;i<=n;i++) { ls=i+1;while(b[i]>1) { while(fl[ls]) ls++;ans+=(long long)(ls-i)*(ls-i);if(ls<=n) b[i]--,b[ls]++,fl[ls]=fl[ls+n]=1;else b[i]--,b[ls-n]++,fl[ls-n]=fl[ls]=1; } }
for(rll i=1;i<=n;i++) { ls=i+1;while(b[i]>1) { while(fl[ls]) ls++;ans+=(long long)(ls-i)*(ls-i);if(ls<=n) b[i]--,b[ls]++,fl[ls]=fl[ls+n]=1;else b[i]--,b[ls-n]++,fl[ls-n]=fl[ls]=1; } }
return ans;
}
int main()
{
n=read();for(rll i=1;i<=n;i++) c[i]=c[n+i]=read();
// 这部分就是优化
for(rll i=1;i<=n;i++)
{
sum[i]=sum[i-1]+c[i];
if(sum[i]<i-st+1) st=i,sum[i]=0;
}
write(sol(st));return 0;
}
C. 自然数
可以发现,左端点不动,mex 的值是随着右端点的移动而单调不递减的.
那么可以先求出所有的 [1,i] 的所有 mex 值,使用线段树维护以当前点为左端点的所有区间的 mex 值(因为它是单调不递减的).
那么在线段树上要实现的操作就是要把从 i 这个位置到下一个与 i 位置值相同的位置的左边一个位置里所有大于这个数的值都变为这个数. 因此线段树需要支持区间修改值与维护区间和、最小值. 因为是单调的,所以可以直接二分右端点找到要修改的区间.
点击查看代码
#include<bits/extc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 200001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rg long long x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
struct node
{
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
ll v,mn,tag=-1;
}t[maxn<<2];
ll n,ls,l,r,mid,ans;
ll a[maxn],cnt[maxn],nxt[maxn],mex[maxn];
__gnu_pbds::gp_hash_table<ll,ll> mp;
#define pushup(rt) t[rt].v=t[ls(rt)].v+t[rs(rt)].v,t[rt].mn=min(t[ls(rt)].mn,t[rs(rt)].mn)
static inline void pushdown(rll rt,rll l,rll r)
{
if(t[rt].tag^-1)
{
rll mid=(l+r)>>1;
t[ls(rt)].v=t[rt].tag*(mid-l+1);t[rs(rt)].v=t[rt].tag*(r-mid);
t[ls(rt)].mn=t[rs(rt)].mn=t[rt].tag;t[ls(rt)].tag=t[rs(rt)].tag=t[rt].tag;
t[rt].tag=-1;
}
}
static inline void build(rll rt,rll l,rll r)
{
if(l==r) { t[rt].v=t[rt].mn=mex[l]; return; }
rll mid=(l+r)>>1;build(ls(rt),l,mid);build(rs(rt),mid+1,r);
pushup(rt);
}
static inline void upd(rll rt,rll l,rll r,rll x,rll y,rll v)
{
if(x<=l&&r<=y) { t[rt].v=v*(r-l+1); t[rt].mn=t[rt].tag=v; return; }
pushdown(rt,l,r);rll mid=(l+r)>>1;
if(x<=mid) upd(ls(rt),l,mid,x,y,v);if(y>mid) upd(rs(rt),mid+1,r,x,y,v);
pushup(rt);
}
static inline ll query_sum(rll rt,rll l,rll r,rll x,rll y)
{
if(x<=l&&r<=y) return t[rt].v;
pushdown(rt,l,r);rll mid=(l+r)>>1,ans=0;
if(x<=mid) ans+=query_sum(ls(rt),l,mid,x,y);if(y>mid) ans+=query_sum(rs(rt),mid+1,r,x,y);
return ans;
}
static inline ll query_min(rll rt,rll l,rll r,rll pos)
{
if(l==r) return t[rt].mn;
pushdown(rt,l,r);rll mid=(l+r)>>1;
if(pos<=mid) return query_min(ls(rt),l,mid,pos);else return query_min(rs(rt),mid+1,r,pos);
}
int main()
{
n=read();for(rll i=1;i<=n;i++) a[i]=read();
for(rll i=1,j=0;i<=n;i++)
{
if(a[i]<=n) cnt[a[i]]++;
while(cnt[j]) j++; mex[i]=j;
}
build(1,1,n);
for(rll i=n;i;i--)
{
if(!mp[a[i]]) nxt[i]=n+1;
else nxt[i]=mp[a[i]];mp[a[i]]=i;
}
for(rll i=1,k;i<=n;i++)
{
ans+=query_sum(1,1,n,i,n);
l=i;r=nxt[i]-1;k=-1;
while(l<r)
{
rll mid=(l+r)>>1;
if(query_min(1,1,n,mid+1)>a[i]) k=mid+1,r=mid;
else l=mid+1;
}
if(k^-1) upd(1,1,n,k,nxt[i]-1,a[i]);
}
write(ans);
return 0;
}
D. 环路
给的是一个 0/1 矩阵,不难想到矩阵乘法.
设 A 矩阵为边的 0/1 矩阵,B 矩阵为单位矩阵(只有 B[i][i]=0, i ∈ [1,n]),那么从 i 到 j 的长度恰好为 k 的路径就是 B × Ak.
那么 就是答案了.
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 201
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rll x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
ll n,k,mod,ans;
char s[maxn];
struct node
{
ll a[maxn][maxn];
inline friend node operator*(rg node a,rg node b)
{
rg node c={0};
for(rll i=1;i<=n;i++)
for(rll j=1;j<=n;j++)
for(rll k=1;k<=n;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
return c;
}
inline friend ll operator+(rll a,rg node b) { for(rll i=1;i<=n;i++) a=(a+b.a[i][i])%mod;return a; }
inline friend void operator*=(rg node& a,rg node b) { a=a*b; }
inline friend void operator+=(rll& a,rg node b) { a=(a+b)%mod; }
}a,c;
int main()
{
n=read();
for(rll i=1;i<=n;i++)
{
scanf("%s",s+1);c.a[i][i]=1;
for(rll j=1;j<=n;j++) a.a[i][j]=s[j]=='Y';
}
k=read();mod=read();
for(rll i=1;i<k;i++) c*=a,ans+=c;
write(ans);
return 0;
}
这样 30 分就到手了.
如何优化?考虑把点 i 与点 n+i 连一条边、点 n+i 与点 n+i 连一条边,这样其自环的步数也就算在内了. 但是由于从 i 走到 n+i 也需要一步,所以矩阵乘法需要乘 k 次方(多乘一次).
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 201
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rll x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
ll n,k,mod,ans;
char s[maxn];
struct node
{
ll a[maxn][maxn];
inline friend node operator*(rg node a,rg node b)
{
rg node c={0};
for(rll i=1;i<=n<<1;i++)
for(rll j=1;j<=n<<1;j++)
for(rll k=1;k<=n<<1;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
return c;
}
inline friend ll operator+(rll a,rg node b) { for(rll i=1;i<=n;i++) a=(a+b.a[i][n+i])%mod;return a; }
inline friend void operator*=(rg node& a,rg node b) { a=a*b; }
inline friend void operator+=(rll& a,rg node b) { a=(a+b)%mod; }
}a,c;
static inline node ksm(rg node a,rll b) { for(rll i=b;i;i>>=1) { if(i&1) c=c*a; a*=a; } return c; }
int main()
{
n=read();
for(rll i=1;i<=n;i++)
{
scanf("%s",s+1);c.a[i][i]=a.a[i][n+i]=a.a[n+i][n+i]=1;
for(rll j=1;j<=n;j++) a.a[i][j]=s[j]=='Y';
}
k=read();mod=read();c=ksm(a,k);
ans+=c;
write((ans-n+mod)%mod);
return 0;
}
--END--
我的博客: 𝟷𝙻𝚒𝚞
本文链接: https://www.cnblogs.com/1Liu/p/16711736.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!