Jigsaw
九条可怜有n盒拼图从1开始编号,当一盒拼图的块数无法组成任何r块*c块的矩形图案时就认为它需要返厂补块。
可怜想知道编号在[l,r]内的拼图,如果选择k个一定需要补块的返厂,那么拼图块数最多的最少是多少。
有时候可怜会发现自己数错了,并将第x盒拼图块数更新成y。
对于100%的数据,1<=n,m,k<=2e5,所有拼图块数在任何时候都是[4,1e6]的整数。保证答案存在
输入格式
第一行两个整数 n,k,m ,m表示可怜询问和修改的数量。
接下来一行 n个正整数,第 i个数表示第 i盒拼图初始时的块数。
接下来 m行 ,每行 ,每3个数 opt,l,r 。为了表明你在即时回答可怜的询问,真 实的 opt,l,r 为输入的 opt,l,r 分别异或( XOR )lastans ,其中 lastans 表示上 一次询问的答案,若之前没有操作则 lastans=0 。
若 opt=1 ,则表示这是一次询问操作的区间为 [l,r] 。
若 opt=2 ,则表示这是一次修改操作把第 l盒拼图的块数修改为 r。
题解
为什么这道题要把输入格式专门弄出来呢,因为这里面有很多东西。
他是强制在线。
先找思路,对于题中的返厂可以推出只要是质数就需要,要是返厂的最大的最小直接贪心,从小的开始选,第k小就是答案。
那么就是求区间第k小,可以用主席树+值域线段树解决。但是他又要修改,就GG。
所以最后只打出了暴力,当修改就清空树,当再需要询问时才建出来。
我们在读一下输入,可以发现k是固定的,这算不算很蹊跷呢?可能有,不过没发现。
OK,现在讲正解,还是从输入下手,发现这个强制在线和一般的不一样:它连opt都要异或。(这有什么特别的?)但是再从数据范围可以挖掘一个东西,那就是ans一定是奇数,因为是质数且>=4。
于是刺激的要来了,当在查询之后(有lastans时),他只有两种操作,且一个奇数一个偶数,由于lastans是奇数,所以opt=1是会读入偶数,opt=2是读入奇数。
对于这个读入我们就可以推出lastans!!!!!!!!!!
所以完整过程就是,先一个一个读入,对于修改在原序列改,当读到一个查询时停止。接着继续读入,如果opt是奇数,那么上一次查询的结果就是opt^2,不然就是opt^1。为了不重复输出,我们可以记录上次询问的答案是否得出,或者下次查询再输出也可以。
读入该异或的还是要异或,在异或时肯定通过这次询问得出了上次的答案的。
如果最后一个查询的答案没得出的话,因为我们在过程中将该修改的都改了,所以得到的就是要查询的序列,对于这个序列操作就OK。可以用桶,跑一遍就好。
树套树的空间会爆炸而且会T。有点东西的

#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
const int maxm=9000005;
int n,m,k,a[maxn];
template<class T>inline void read(T &x){
x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}
const int maxk=1000000;
bool not_prime[maxk+6];
int prime[maxk];
int c[maxk+6];
void pre(){
not_prime[1]=true;
for(int i=2;i<=maxk;i++){
if(!not_prime[i])
prime[++prime[0]]=i;
for(int j=1;j<=prime[0]&&i*prime[j]<=maxk;j++){
not_prime[i*prime[j]]=true;
if(!(i%prime[j])) break;
}
}
}
int main(){
freopen("jigsaw.in","r",stdin);
freopen("jigsaw.out","w",stdout);
pre();
read(n);read(k);read(m);
for(int i=1;i<=n;i++) read(a[i]);
int ans=0;
int opt,l,r;
while(m--){
read(opt);read(l);read(r);
if(opt==2) a[l]=r;
else break ;
}
bool prin=false;//当前询问是否输出
while(m--){
read(opt);read(l);read(r);
if(opt&1){
if(!prin){
printf("%d\n",ans=opt^2);
prin=true;
}
a[l^ans]=r^ans;
}
else {
if(!prin)
printf("%d\n",ans=opt^1);
prin=false;
}
}
if(!prin){
l^=ans;r^=ans;
for(int i=l;i<=r;i++)
if(!not_prime[a[i]])
c[a[i]]++;
for(int i=1;i<=prime[0];i++)
if(c[prime[i]]<k) k-=c[prime[i]];
else {
printf("%d",prime[i]);
return 0;
}
}
}
Baritone
有一个r*c的网格,有n个上低音号手,每个上低音号手会被安排到其中一个格子且互不相同。摄影师会拍摄照片,每个照片都是一个边框平行于坐标轴的矩形,当照片内出现至少k个上低音号手时,久美子就会喜欢,询问究竟能拍出多少张让久美子满意的照片。
1<=n,r,c<=3000,1<=k<=10
题解
doggu在DAY1的课件上的题,结果就只看懂了第二个方法:枚举上下边界,先固定左边界p,移动右边界q直到恰满足条件,这样就有c-q+1种方案,然后移动左边界....其实就是双指针,双指针复杂度为O(n),于是这种方法就是O(n3),得到了30opts的高分(?????)
在一波询问之后终于对正解有了一点理解,代码里面(照着std)写的是枚举左边界,那就用左边界讲解。
我们在固定一个左边界之后,右边界初始在最右边,没有上下边界,将这区域的点排序(以行为第一关键字,列为第二关键字),然后就可以形成一个链表,考虑如果点x是从上边界下来遇到的第一个节点,他的前驱pre和往后跳(k-1)的后继 next,这个点的val就是(row[x]-row[pre])*(r-row[next]+1),就是上下边界的活动范围的乘积。
记录下所有在区域内的点的val的和ret,现在考虑将右边界缩小,假设x点变成区域外,那么ret-=val[x],更新链表,并且next[x]和x往上的k-1个点的val都需要更新,还有ret也要更新。
如何知道移动这个右边界会影响哪些点,对于每列开个vector就好了。
排序那一块,看着std的打的,dx,dy记录点的坐标,id就是每个点的编号,mp是原来的人的筛选后的编号。这种cmp的写法只改变id数组,使得(dx[id[i]],dy[id[i]])变得有序。
要多存入一些边界点,防止出界。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=3025;
int n,r,c,k;
int x[maxn],y[maxn];
int dx[maxn],dy[maxn],id[maxn],mp[maxn];
int pre[maxn],nxt[maxn],row[maxn];
vector<int> col[maxn];
ll ret,ans,val[maxn];
template<class T>inline void read(T &x){
x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}
bool cmp(int a,int b){
if(dx[a]==dx[b]) return dy[a]<dy[b];
return dx[a]<dx[b];
}
void del(int t){
nxt[pre[t]]=nxt[t];
pre[nxt[t]]=pre[t];
ret-=val[t];
int x=nxt[t],y=nxt[t];
for(int i=1;i<k;i++)
y=nxt[y];
for (int i=1;i<=k;i++) {
ll v=(dx[x]-dx[pre[x]])*(r-dx[y]+1);
ret+=v-val[x];
val[x]=v;
x=pre[x];
y=pre[y];
}
}
ll get(int l){
int cnt=0;ll sum=0;
ret=0;
for(int i=1;i<=n;i++)
if(y[i]>=l){
dx[++cnt]=x[i];
dy[cnt]=y[i];
mp[i]=cnt;
}
for(int i=1;i<=10;i++) dx[++cnt]=0,dx[++cnt]=r+1;
for(int i=1;i<=cnt;i++) id[i]=i;
sort(id+1,id+cnt+1,cmp);
for(int i=1;i<cnt;i++){
pre[id[i+1]]=id[i];
nxt[id[i]]=id[i+1];
}
pre[id[1]]=id[1];
nxt[id[cnt]]=id[cnt];
for(int i=1;i<=cnt;i++){
int now=i;
for(int j=1;j<k;j++) now=nxt[now];
val[i]=(dx[i]-dx[pre[i]])*(r-dx[now]+1);
ret+=val[i];
}
for(int i=c;i>=l;i--){
sum+=ret;
for(unsigned int j=0;j<col[i].size();j++)
del(mp[col[i][j]]);
}
return sum;
}
int main(){
freopen("baritone.in","r",stdin);
freopen("baritone.out","w",stdout);
read(r);read(c);read(n);read(k);
for(int i=1;i<=n;i++){
read(x[i]);read(y[i]);
col[y[i]].push_back(i);
}
for(int i=1;i<=c;i++)
ans+=get(i);
printf("%lld",ans);
}
