《The 2023 Guangdong Provincial Collegiate Programming Contest》vp记录
队伍配置:
和我这个菜鸡。
膜拜另外两个大佬
赛况:
\(PS:\) 看高二的在那边打感觉挺有趣的我们也跑过来打了。
首先我把 \(A\) 签到题给签了,然后去看 \(D\) , \(gsc\) 去看 \(C\) ,这时候 \(lyq\) 大佬还没有加入战场,还在调自己的题,不过问题不大。
我很快看出了 \(D\) 的贪心,然后这时候 \(lyq\) 加入战场,直接开 \(F\) ,\(\%\%\%\) 。我很快写完 \(D\) ,结果死活过不去,吃了两发罚时还没有做出来, \(gsc\) 也发现自己看错题了,然后 \(lyq\) 已经开码 \(F\) 的树套树了。
没过多久 \(gsc\) 的 \(C\) 写完了,过来帮我调 \(D\) ,然后我看 \(I\) 很多人过了,就去写 \(I\) ,让 \(gsc\) 坐牢帮我调题。然后很快 \(lyq\) 的树套树写好了,结果发现 \(\log^2\) 跑不过去,只好换线段树二分。然后我很快写了 \(I\) ,结果又是罚时,有没有过,然后又是坐牢调题。
过了一会, \(gsc\) 说他帮我找到了 \(hack\) 数据,然后我自己就在那边改, \(gsc\) 又去帮我调 \(I\) ,然后我也不知道他怎么改,帮我过掉了 \(I\) 。然后我改我的 \(D\) ,结果还是过不去,吃了 \(4\) 发罚时了,恼羞成怒。拍案而起,润去写 \(B\) 。
一个小时多一点的时候, \(lyq\) 大佬凭借强大的码力通过了那个大数据结构题,然后来帮忙我写 \(D\) 。
过了 \(20\) 分钟, \(gsc\) 把 \(K\) 切了,然后又过了 \(10\) 分钟,我把 \(B\) 写了。
这时候改签的到差不多签完了,还剩 \(D,E\) 是比较可做的题。
这时候 \(lyq\) 写好了 \(D\) ,但是没过,让我帮他调,帮他调的时候我突然意识到自己哪里写错了,然后把我的 \(D\) 改了一下交上去就过了,这样我们就还差 \(E\) ,就基本上可以打卡下班了。
一开始 \(E\) 我以为是 \(sa\) ,然后乱搞,然后 \(lyq\) 大佬 \(trie\) 树排排序,然后二分就秒了,他写了差不多一个小时写完了,这时候赛事排名已经来到了 \(29\) 。
这时候 \(gsc\) 大佬在做 \(M\) ,他直接现场学习旋转卡壳,然后后来发现假了。
我就看看 \(G\) 看看 \(H\) 发现都不会做,然后摆烂,提前下班。
然后就结束了,最后排名这鸟样。

这把 \(D\) 属实是有点坑逼了。
来写写题解:
\(A\)
这题还要题解???
点击查看代码
#include<bits/stdc++.h>
typedef long long Ll;
using namespace std;
const int MAXN=1e5+10;
int T,sta,n,a[MAXN],vis[MAXN];
int main () {
scanf("%d",&T);
while(T--) {
scanf("%d",&sta);
scanf("%d",&n);
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
vis[a[i]]=1;
}
int ed;
scanf("%d",&ed);
int ans=0;
for(int i=sta;i<=ed;++i) ans+=(!vis[i]);
printf("%d\n",ans);
}
return 0;
}
\(B\)
记一个状态 \(f_i\) 表示前 \(i\) 个选完且满足了那些已经结尾的区间且第 \(i\) 个必选的最小代价。
\(f_i=\min f_j+a_i\)
如果一个区间结束了,那么这个区间左端点左边的那些决策的都不能选,赋值成无限大即可。
然后我就写了一个线段树去优化他,实际上不用,直接单调队列即可。
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=5e5+10;
const LL inf=1e18+10;
int T,n;
int a[MAXN];
vector<int> e[MAXN];
struct ddl {
LL a;
LL lb;
}tr[MAXN*4];
void psup(int u) {
tr[u].a=min(tr[(u<<1)].a,tr[(u<<1|1)].a);
}
void build(int u,int l,int r) {
tr[u].lb=0;
if(l==r) {
tr[u].a=inf;
return ;
}
int mid=(l+r)/2;
build((u<<1),l,mid);
build((u<<1|1),mid+1,r);
psup(u);
}
void zx(int x) {
tr[x].lb=1;
tr[x].a=inf;
}
void psdn(int u) {
if(tr[u].lb) {
zx((u<<1));
zx((u<<1|1));
tr[u].lb=0;
}
}
void update(int u,int l,int r,int x,LL y) {
if(l>x||r<x) return ;
if(l==r) {
tr[u].a=min(tr[u].a,y);
return ;
}
int mid=(l+r)/2;
psdn(u);
update((u<<1),l,mid,x,y);
update((u<<1|1),mid+1,r,x,y);
psup(u);
}
void modify(int u,int l,int r,int x,int y) {
if(l>y||r<x) return ;
if(l>=x&&r<=y) {
zx(u);
return ;
}
int mid=(l+r)/2;
psdn(u);
modify((u<<1),l,mid,x,y);
modify((u<<1|1),mid+1,r,x,y);
psup(u);
}
LL query(int u,int l,int r,int x,int y) {
if(l>y||r<x) return inf;
if(l>=x&&r<=y) return tr[u].a;
int mid=(l+r)/2;
psdn(u);
return min(query((u<<1),l,mid,x,y),query((u<<1|1),mid+1,r,x,y));
}
int main () {
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
e[i].clear();
}
int q;
scanf("%d",&q);
for(int i=1;i<=q;++i) {
int l,r;
scanf("%d%d",&l,&r);
e[r].push_back(l);
}
build(1,0,n);
update(1,0,n,0,0);
for(int i=1;i<=n;++i) {
LL xi=query(1,0,n,0,i-1);
for(auto t:e[i]) {
modify(1,0,n,0,t-1);
}
update(1,0,n,i,xi+a[i]);
}
printf("%lld\n",query(1,0,n,0,n));
}
return 0;
}
\(C\)
直接贪心即可,排个序,从大往小选。
\(D\)
贪心,记 \(c_i=b_i-a_i\) 那么我们把 \(a\) 数组按 \(c\) 排序,然后能尽量选就选,然后判一下情况即可。
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=5e5+10;
int T,n,m;
struct ddl {
int a,b,c;
}a[MAXN];
bool cmp(ddl a,ddl b) {
return a.c>b.c;
}
int main () {
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
LL sum=0;
for(int i=1;i<=n;++i) {
scanf("%d%d",&a[i].a,&a[i].b);
a[i].c=a[i].b-a[i].a;
sum+=a[i].a;
}
if(n==1) {
printf("%d\n",a[1].b);
continue;
}
sort(a+1,a+1+n,cmp);
int p=m-n,ll=min(p,n),lt=0;
for(int i=1;i<=ll;++i) {
if(a[i].c<0) break;
sum+=a[i].c; lt=i;
}
if(lt==n-1) {
sum+=a[n].c;
sum=max(sum,sum-a[n].c-a[n-1].c);
}
printf("%lld\n",sum);
}
return 0;
}
\(E\)
给你 \(n\) 个串,选 \(k\) 个串,使其中两两 \(lcp\) 的最大最小。
一开始我以为选数一定是相邻的最优,然后想用 \(sa\) 乱搞,实则不然,你选不相邻的可以使得可能的最大小一点。
看到最大最小我们可以考虑二分我们最后的答案的字典序,就是把所有串排序,然后答案只可能是他们的前缀,然后二分这些前缀就可以了。
每次能选就选,因为肯定是当前穿串与上一个串越远越好,然后这题就做完了。
不过还有一个细节,怎么排序,可以用字典树排序,当然你用 \(sa\) 也不是不行。
\(F\)
其实我们就是要找到左边或者右边的最远到哪里。
我们首先可以二分,但是如果直接二分加动态开点时间复杂度是 \(O(k\log^2 n)\)
可以直接线段树上二分可以做到 \(O(k\log n)\)
我写的代码:
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=1e5+10;
int TT;
int n,m;
int val[MAXN],col[MAXN];
struct tr_arr {
LL tr[MAXN];
int lowbit(int x) {
return x&(-x);
}
void update(int x,int v) {
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=v;
}
LL qry(int x) {
LL res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
LL query(int l,int r) {
return qry(r)-qry(l-1);
}
void clear() {
for(int i=1;i<=n;++i) tr[i]=0;
}
}T;
struct daduoli {
int ls,rs,s;
}tr[MAXN*40];
int rt[MAXN],cnt,k;
void update(int &u,int l,int r,int x,int y) {
if(!u) u=++cnt;
tr[u].s+=y;
if(l==r) return ;
int mid=(l+r)/2;
if(x<=mid) update(tr[u].ls,l,mid,x,y);
else update(tr[u].rs,mid+1,r,x,y);
}
int query(int u,int l,int r,int x,int y) {
if(!u||l>y||r<x) return 0;
if(l>=x&&r<=y) return tr[u].s;
int mid=(l+r)/2;
return query(tr[u].ls,l,mid,x,y)+query(tr[u].rs,mid+1,r,x,y);
}
int a[MAXN],b[MAXN];
int qrL(int l,int r,int x) {
if(l==r) return l;
int mid=(l+r)/2;
int ls=0;
for(int i=1;i<=k;++i) ls+=tr[tr[a[i]].rs].s;
if((n-mid+1)<=x+ls) {
for(int i=1;i<=k;++i) a[i]=tr[a[i]].ls;
return qrL(l,mid,x+ls);
}
else {
for(int i=1;i<=k;++i) a[i]=tr[a[i]].rs;
return qrL(mid+1,r,x);
}
}
int qrR(int l,int r,int x) {
if(l==r) return l;
int mid=(l+r)/2;
int ls=0;
for(int i=1;i<=k;++i) ls+=tr[tr[a[i]].ls].s;
if((mid+1)<=x+ls) {
for(int i=1;i<=k;++i) a[i]=tr[a[i]].rs;
return qrR(mid+1,r,x+ls);
}
else {
for(int i=1;i<=k;++i) a[i]=tr[a[i]].ls;
return qrR(l,mid,x);
}
}
int main () {
scanf("%d",&TT);
while(TT--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&col[i]);
}
for(int i=1;i<=n;++i) {
scanf("%d",&val[i]);
T.update(i,val[i]);
update(rt[col[i]],0,n+1,i,1);
}
int opt,x;
for(int i=1;i<=m;++i) {
scanf("%d%d%d",&opt,&x,&k);
if(opt==1) {
update(rt[col[x]],0,n+1,x,-1);
col[x]=k;
update(rt[col[x]],0,n+1,x,1);
}
if(opt==2) {
T.update(x,k-val[x]);
val[x]=k;
}
if(opt==3) {
for(int j=1;j<=k;++j) {
scanf("%d",&a[j]);
a[j]=rt[a[j]]; b[j]=a[j];
}
int sum=0;
for(int j=1;j<=k;++j) {
sum+=query(a[j],0,n+1,x+1,n);
}
sum=((n+1)-x)-sum;
int L=qrL(0,n+1,sum); ++L;
sum=0;
for(int j=1;j<=k;++j) {
a[j]=b[j];
sum+=query(a[j],0,n+1,1,x-1);
}
sum=(x-1)-sum+1;
int R=qrR(0,n+1,sum); --R;
printf("%lld\n",T.query(L,R));
}
}
for(int i=1;i<=cnt;++i) {
tr[i].ls=tr[i].rs=tr[i].s=0;
}
for(int i=1;i<=n;++i) rt[i]=0;
cnt=0;
T.clear();
}
return 0;
}
贺 \(lyq\) 的代码,但是不知道为什么没过
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=1e5+10;
int TT;
int n,m;
int val[MAXN],col[MAXN];
struct tr_arr {
LL tr[MAXN];
int lowbit(int x) {
return x&(-x);
}
void update(int x,int v) {
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=v;
}
LL qry(int x) {
LL res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
LL query(int l,int r) {
return qry(r)-qry(l-1);
}
void clear() {
for(int i=1;i<=n;++i) tr[i]=0;
}
}T;
struct daduoli {
int ls,rs,s;
}tr[MAXN*40];
int rt[MAXN],cnt,k;
void update(int &u,int l,int r,int x,int y) {
if(!u) u=++cnt;
tr[u].s+=y;
if(l==r) return ;
int mid=(l+r)/2;
if(x<=mid) update(tr[u].ls,l,mid,x,y);
else update(tr[u].rs,mid+1,r,x,y);
}
int a[MAXN],b[MAXN];
vector<int> e;
int qrL(vector<int> p,int l,int r,int x) {
if(l>x) return 0;
if(r<=x) {
int ls=0;
for(int i=0;i<k;++i) ls+=tr[p[i]].s;
if(ls==r-l+1) return l;
}
if(l==r) return 0;
int mid=(l+r)/2;
vector<int> bl,br;
for(int i=0;i<k;++i) bl.push_back(tr[p[i]].ls),br.push_back(tr[p[i]].rs);
if(x<=mid) return qrL(bl,l,mid,x);
else {
int res=qrL(br,mid+1,r,x);
if(res==mid+1) {
int ls=qrL(bl,l,mid,x);
if(ls) res=ls;
}
return res;
}
}
int qrR(vector<int> p,int l,int r,int x) {
if(r<x) return 0;
if(l>=x) {
int ls=0;
for(int i=0;i<k;++i) ls+=tr[p[i]].s;
if(ls==r-l+1) return r;
}
if(l==r) return 0;
int mid=(l+r)/2;
vector<int> bl,br;
for(int i=0;i<k;++i) bl.push_back(tr[p[i]].ls),br.push_back(tr[p[i]].rs);
if(x>mid) return qrR(br,mid+1,r,x);
else {
int res=qrR(bl,l,mid,x);
if(res==mid) {
int ls=qrR(br,mid+1,r,x);
if(ls) res=ls;
}
return res;
}
}
int main () {
scanf("%d",&TT);
while(TT--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&col[i]);
}
for(int i=1;i<=n;++i) {
scanf("%d",&val[i]);
T.update(i,val[i]);
update(rt[col[i]],1,n,i,1);
}
int opt,x;
for(int i=1;i<=m;++i) {
scanf("%d%d%d",&opt,&x,&k);
if(opt==1) {
update(rt[col[x]],1,n,x,-1);
col[x]=k;
update(rt[col[x]],1,n,x,1);
}
if(opt==2) {
T.update(x,k-val[x]);
val[x]=k;
}
if(opt==3) {
e.clear();
for(int j=1;j<=k;++j) {
scanf("%d",&a[j]);
a[j]=rt[a[j]];
e.push_back(a[j]);
}
int L=qrL(e,1,n,x);
int R=qrR(e,1,n,x);
printf("%lld\n",T.qry(R)-T.qry(L-1));
}
}
for(int i=1;i<=cnt;++i) {
tr[i].ls=tr[i].rs=tr[i].s=0;
}
for(int i=1;i<=n;++i) rt[i]=0;
cnt=0;
T.clear();
}
return 0;
}
\(G\)
首先对于这种有关位运算的,我们有一个经典 \(trick\) ,很多类型的位运算他们至多改变次数是 \(\log n\) 次, \(\&\) 就是如此。
所以我们考虑到他的前缀 \(\&\) 值,和后缀 \(\&\) 值最多只有 \(\log n\) 个不同。
我们考虑记改变 \(\&\) 值的位置为关键点。
那么现在有三种交换,不过你交换你得有断点才有意义,否则你连断点都不知道在哪里,你交换了又有什么用呢。
- 非关键点和非关键点
显然如果两个非关键点交换不会带来任何贡献。
- 关键点和关键点
这个我们可以直接枚举,因为两边都只有 \(\log n\) 个,所以你枚举至多只有 \(\log^2 n\) 个。总时间复杂度 \(O(n\log^2 n)\)
- 关键点和非关键点
考虑枚举前缀关键点,那么贡献就是 \(f(1,i-1)\& f(i+1,k)\& a_j+f(k+1,n)\& a_i\)
后面那个式子不管 \(j\) 是什么都是固定的,所以我们现在就是要找到最大的, \(f(1,i-1)\& f(i+1,k)\& a_j\) ,因为 \(f(1,i-1)\) 至多只有 \(\log n\) 个,而对于每个关键点至多有 \(\log n\) 个 \(f(i+1,k)\) ,所以 \(f(1,i-1)\& f(i+1,k)\) 至多只有 \(\log^2 n\) 个,所以对于每个这个东西都暴力枚举即可。后缀关键点同理
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=1e5+10;
int T;
int n,a[MAXN];
int st[MAXN][20],logg[MAXN];
void init() {
for(int i=2;i<=n;++i) logg[i]=logg[i/2]+1;
for(int i=1;i<=logg[n];++i) {
int R=(n-(1<<i))+1;
for(int j=1;j<=R;++j) {
st[j][i]=(st[j][i-1]&st[j+(1<<(i-1))][i-1]);
}
}
}
int query(int l,int r) {
if(l>r) return (1ll<<31)-1;
int k=r-l+1; k=logg[k];
return (st[l][k]&(st[r-(1<<k)+1][k]));
}
unordered_map<int,vector<int> > f;
unordered_map<int,vector<int> > g;
int get_g(int v,int i) {
if(!g.count(v)) {
vector<int> ls;
ls.resize(n+2);
for(int i=n;i>=1;--i) ls[i]=max(ls[i+1],(v&a[i]));
g[v]=ls;
}
return g[v][i];
}
int get_f(int v,int i) {
if(!f.count(v)) {
vector<int > ls;
ls.resize(n+1);
for(int i=1;i<=n;++i) ls[i]=max(ls[i-1],(v&a[i]));
}
return f[v][i];
}
int main () {
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
st[i][0]=a[i];
}
init();
//get imp
vector<int> pre,suf;
int x=(1ll<<31)-1;
for(int i=1;i<=n;++i) {
if((x&a[i])!=x) pre.push_back(i);
x&=a[i];
}
x=(1ll<<31)-1;
for(int i=n;i>=1;--i) {
if((x&a[i])!=x) suf.push_back(i);
x&=a[i];
}
int ans=0;
//no swap
for(int i=1;i<n;++i) {
ans=max(ans,query(1,i)+query(i+1,n));
}
//imp and imp
for(int i=1;i<n;++i) {
for(auto l:pre) {
if(l>i) break;
for(auto r:suf) {
if(r<=i) break;
ans=max(ans,(query(1,l-1)&query(l+1,i)&a[r])+(query(i+1,r-1)&query(r+1,n)&a[l]));
}
}
}
//pre imp and suf iimp
g.clear();
for(int i=1;i<=n;++i) {
for(auto l:pre) {
if(l>i) break;
int val=(query(i+1,n)&a[l]);
int v=get_g((query(1,l-1)&query(l+1,i)),i+1);
ans=max(ans,val+v);
}
}
//suf imp and pre iimp
f.clear();
for(int i=n;i>=1;--i) {
for(auto r:suf) {
if(r<=i) break;
int val=(query(1,i)&a[r]);
int v=get_g((query(i+1,r-1)&query(r+1,n)),i);
ans=max(ans,val+v);
}
}
printf("%d\n",ans);
}
return 0;
}
\(H\)
首先对于这样的二元组我们有一种感觉,就是在他们之间建边,把它们转化成图论。
不过由于考试的时候没看到只有 \(1,2\) 就压根没有思路。
由于每个位置只会被最后一次操作影响我们考虑倒着做,先取哪些位置。
我们考虑有哪些操作
- 假如有一个操作是 \(x,2,y,2\)
那么这个操作一定是最优的,他一定是第一个选择的
- 如果有一个操作是 \(x,1,y,1\)
那么一定是最劣的,一定是最后选择的。
- 如果有一个操作是 \(x,2,y,1\) 或者 \(x,1,y,2\)
那么这是无法辨别的,我们考虑对于修改成 \(1\) 的向修改成 \(2\) 的位置连一个边。
这样从一个点(这个点选 \(1\) )到达的所有点都是能被锁定成 \(2\) 的,然后我们就是要让锁定成 \(2\) 的最多。
但是图不好做,我们缩成 \(DAG\) 图,因为一个 \(SCC\) 中点可以互相到达,所以只要有一个点被遍历,那么整个 \(SCC\) 都可以被遍历。
所以我们就从入度为 \(0\) 的边开始遍历,然后所有入度为 \(0\) 的边都遍历一下就好了。
注意事项:对于被 \(x,2,y,2\) 这样操作锁定成 \(2,2\) 的点,在遍历过程中我们优先遍历这些点,因为这些点一定是 \(2\)
时间复杂度 \(O(n+m)\)
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=5e5+10;
int T;
int n,m;
int o[MAXN][4];
vector<int> ans,vp,ls;
struct ddl {
int t,c;
};
vector<ddl> e[MAXN];
void add(int f,int t,int c) {
e[f].push_back({t,c});
}
int dfn[MAXN],low[MAXN],ind[MAXN],tot,cnt;
int cir,scc[MAXN],val[MAXN];
bool vis[MAXN];
void tarjan(int u) {
dfn[u]=low[u]=++cnt;
ind[++tot]=u; vis[u]=1;
for(auto t:e[u]) {
int T=t.t;
if(!dfn[T]) {
tarjan(T);
low[u]=min(low[u],low[T]);
}
else if(vis[T]) low[u]=min(low[u],dfn[T]);
}
if(dfn[u]==low[u]) {
++cir;
while(1) {
scc[ind[tot]]=cir;
vis[ind[tot]]=0;
--tot;
if(ind[tot+1]==u) break;
}
}
}
int deg[MAXN];
bool sf[MAXN];
void dfs(int u) {
if(sf[u]) return ;
sf[u]=1;
for(auto t:e[u]) {
int T=t.t;
ls.push_back(t.c);
dfs(T);
}
}
void vmain() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
for(int j=0;j<4;++j) scanf("%d",&o[i][j]);
if(o[i][1]==1&&o[i][3]==1) {
ans.push_back(i);
}
if(o[i][1]==1&&o[i][3]==2) add(o[i][0],o[i][2],i);
if(o[i][1]==2&&o[i][3]==1) add(o[i][2],o[i][0],i);
if(o[i][1]==2&&o[i][3]==2) {
vp.push_back(i);
}
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i) {
for(auto t:e[i]) {
if(scc[i]!=scc[t.t]) ++deg[scc[t.t]];
}
}
for(auto t:vp) {
if(!deg[scc[o[t][0]]]) dfs(o[t][0]);
if(!deg[scc[o[t][2]]]) dfs(o[t][2]);
}
for(int i=1;i<=n;++i) if(!deg[scc[i]]) dfs(i);
reverse(ls.begin(),ls.end());
for(auto t:ls) {
ans.push_back(t);
}
for(auto t:vp) {
ans.push_back(t);
}
for(auto t:ans) {
val[o[t][0]]=o[t][1];
val[o[t][2]]=o[t][3];
}
int cc=0;
for(int i=1;i<=n;++i) cc+=val[i];
printf("%d\n",cc);
for(auto t:ans) {
printf("%d ",t);
}
puts("");
cnt=tot=cir=0;
ans.clear(); vp.clear(); ls.clear();
for(int i=1;i<=n;++i) {
e[i].clear();
vis[i]=sf[i]=0;
dfn[i]=low[i]=scc[i]=val[i]=deg[i]=0;
}
}
int main () {
scanf("%d",&T);
while(T--) vmain();
return 0;
}
\(I\)
二分 \(mex\) ,然后排序,时间复杂度 \(O(nm\log^2(nm))\)
实际上不需要排序,可以直接枚举所有小于等于 \(mid\) 的值,这样得到的数其实已经拍好序了。
时间复杂度 \(O(nm\log (nm))\)
我实现的没有那么精细,写了 \(\log^2\) 的。
点击查看代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=1e6+10;
int T,n,m;
int a[MAXN],b[MAXN];
struct ddl {
int x,y;
}d[MAXN];
int cnt;
bool cmp(ddl a,ddl b) {
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
bool check(int x) {
cnt=0;
for(int i=0;i<=x;++i) {
d[++cnt].x=a[i];
d[cnt].y=b[i];
}
sort(d+1,d+1+cnt,cmp);
int y=0;
for(int i=1;i<=cnt;++i) {
if(y>d[i].y) return false;
y=d[i].y;
}
return true;
}
int erfind() {
int l=0,r=n*m,mid;
while(l+1<r) {
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
return l;
}
int main () {
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
int x;
scanf("%d",&x);
a[x]=i; b[x]=j;
}
}
printf("%d\n",erfind()+1);
}
return 0;
}
\(J\)
题解没看太懂,不过懂大概思想就好了。
-
首先如果序列只有一位,那么直接判断 \(x=y\) 就好了。
-
如果序列有两位,那么肯定满足 \(a^2>x\)
我们设最高位是 \(t\) ,满足 \(t<a\) 。
又因为低位相同,所以 \(x-ta=y-tb\) ,所以有 \(b=\frac {y-x}t+a\)
然后我们要满足一些条件,低位 \(<a\) 。然后还要满足 \(2\le a\le A,2\le b\le B\)
因为 \(b\) 为整数,所以 \(t\) 至多只有 \(\sqrt n\) 个。
然后还要推一些式子,我不太会推,反正对着题解抄就完了。
- 如果序列大于两位,那么这样的至多只有 \(\sqrt n\) 个
然后暴力枚举一下,双指针就好了,时间复杂度 \(O(\sqrt n\log n)\)
点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int T;
int x,y,A,B;
LL gao(int a,int b) {
LL ls=x,sum=0,pw=1;
while(ls) {
sum+=ls%a*pw;
ls/=a; pw*=b;
if(sum>y) return sum;
}
return sum;
}
bool check(int a,int b) {
LL xx=x,yy=y;
while(xx&&yy) {
if(xx%a!=yy%b) return false;
xx/=a; yy/=b;
}
return (xx==yy);
}
LL ceil(LL a,LL b) {
return (a+b-1)/b;
}
void vmain() {
scanf("%d%d%d%d",&x,&y,&A,&B);
//1
if(x==y) {
puts("YES");
puts("2 2");
return ;
}
//2
for(LL t=1;t*t<=x&&t<=A;++t) {
if((y-x)%t==0) {
LL l=2,r=A;
l=max(l,ceil(2*t+x-y,t));
l=max(l,x/(t+1)+1);
l=max(l,t+1);
l=max(l,((t+1)*x-y)/(t*(t+1))+1);
l=max(l,(t*t+x-y)/t+1);
r=min(r,(t*B+x-y)/t);
r=min(r,x/t);
if(l<=r) {
puts("YES");
printf("%d %d\n",l,(t*l-x+y)/t);
return ;
}
}
}
//3
for(int a=2,b=2;a*a<=x&&a<=A;++a) {
while(gao(a,b)<y&&b*b<=y&&b<=B) ++b;
if(b*b>y||b>B) break;
if(gao(a,b)==y&&check(a,b)) {
puts("YES");
printf("%d %d\n",a,b);
return ;
}
}
puts("NO");
}
int main () {
scanf("%d",&T);
while(T--) {
vmain();
}
return 0;
}
\(K\)
暴力 \(dfs\) 即可。

浙公网安备 33010602011771号