2024-03-14
Riddle
继续做上次没做出来的题
2-SAT
限制是
- 如果一个点不选,那么与它相连的所有点都必须选
- 如果一个点选了,那么和他在同一个部分的所有点都不能选
对于边的限制直接建
但是“部分”的限制直接建图是 \(O(n^2)\) 的
优化方法是 前缀优化建图
对于每一个部分,用 \(a_i\) 表示这一个部分的第 \(i\) 个点
\(p_{a_i}\) 表示 \(a_i\) 这个点之前的点有没有被选上的
那么直接按下面的方式建边即可
\[a_i \longrightarrow p_{a_i} \ \ \ \ \ {p_{a_i}}^{\prime} \longrightarrow {a_i}^{\prime} \ \ \ \ \ p_{a_{i-1}} \longrightarrow {a_i}^{\prime} \ \ \ \ \ a_i \longrightarrow {p_{a_{i-1}}}^{\prime} \ \ \ \ \ p_{a_{i-1}} \longrightarrow p_{a_i} \ \ \ \ \ {p_{a_i}}^{\prime} \longrightarrow {p_{a_{i-1}}}^{\prime} \]这样的建图和原来等价
#include<iostream>
#include<cstring>
#include<algorithm>
#define v1(x) (x)
#define v0(x) (x+n)
#define p1(x) (x+n+n)
#define p0(x) (x+n+n+n)
using namespace std;
const int N=1e6+100;
const int V=4*N,E=2*V;
int n,m,k;
int w,a[N];
int hd[V],edg[E],nxt[E],idx;
void adde(int u,int v) {
edg[idx]=v,nxt[idx]=hd[u],hd[u]=idx++;
}
int grp[V],cnt;
int dfn[V],low[V],idt;
int stk[V],top;
bool st[V];
void Tarjan(int u) {
dfn[u]=low[u]=++idt;
stk[++top]=u,st[u]=true;
for(int i=hd[u];~i;i=nxt[i]) {
int v=edg[i];
if(!dfn[v]) {
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(st[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
int v;
cnt++;
do {
v=stk[top--];
st[v]=false;
grp[v]=cnt;
}while(v!=u);
}
}
int main() {
memset(hd,-1,sizeof(hd));
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) {
int u,v;
scanf("%d%d",&u,&v);
adde(v0(u),v1(v)),adde(v0(v),v1(u));
}
for(int i=1;i<=k;i++) {
scanf("%d",&w);
for(int j=1;j<=w;j++) scanf("%d",&a[j]);
for(int j=1;j<=w;j++) {
adde(v1(a[j]),p1(a[j])),adde(p0(a[j]),v0(a[j]));
if(j>1) {
adde(p1(a[j-1]),p1(a[j])),adde(p0(a[j]),p0(a[j-1]));
adde(p1(a[j-1]),v0(a[j])),adde(v1(a[j]),p0(a[j-1]));
}
}
}
for(int i=1;i<=4*n;i++) if(!dfn[i]) Tarjan(i);
string ans="TAK";
for(int i=1;i<=n;i++) {
if(grp[v0(i)]==grp[v1(i)]) {
ans="NIE";
break;
}
}
cout<<ans<<endl;
return 0;
}
整体二分
复习整体二分
整体二分用于处理
- 多次询问 可以离线
- 答案可以二分
我们可以对于每个询问都猜测它的答案是 mid 然后分为 不大于 mid 和 大于 mid 两个部分继续分治 l==r 时结束这个部分的分治
引用题解的一段话理解
显然可以给每个询问二分答案,统计该询问中小于等于mid的元素个数。如果大于等于k,说明猜大了,否则说明猜小了。
如果用这种方法的话,对于每个询问都至少要用O(询问矩阵大小*log值域)的时间复杂度解决,多组询问的话时间不能接受。
发现多个询问的二分答案是可以同时被检验的,我们可以为所有询问同时二分答案,把所有答案小于等于mid的询问放在询问序列的左侧,大于mid的放到询问序列的右侧然后递归处理。
Dynamic Rankings
板子题:区间带修改第 K 大
不贴代码占地了
矩阵乘法
# 题目描述 #
给你一个 \(n×n\) 的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第 \(k\) 小数。
把树状数组改成二维的
把值小于 mid 的所有点涂黑,就能查询每个询问的子矩阵里面小于等于 mid 的点的个数了
因为要清空,所以记录一个 cur 数组,每次如果这个询问要被分到右边的话就更新一下
树状数组不要写 while 循环了,不知道怎么就寄了。。改成for循环就对了
for(int i=x;i<=n;i+=lowbit(i)) for(int j=y;j<=n;j+=lowbit(j)) //...
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510,M=60010;
int n,m;
struct BIT {
int lowbit(int x) {
return x&-x;
}
int n;
int tr[N][N];
void update(int x,int y,int k) {
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
tr[i][j]+=k;
}
int query(int x,int y) {
int res=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
res+=tr[i][j];
return res;
}
int submatr(int x1, int y1, int x2, int y2) {
return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);
}
}T;
struct Num {
int x,y;
int val;
friend bool operator < (Num A,Num B) {
return A.val<B.val;
}
}mat[N*N];
int cm;
struct Query {
int x1,y1,x2,y2,k;
}q[M];
int id[M];
int t1[M],t2[M];
int ans[M],cur[M];
int count_black(Query Q) {
return T.submatr(Q.x1,Q.y1,Q.x2,Q.y2);
}
#define mid ((lft+rgh)/2)
void solve(int lft,int rgh,int ql,int qr) {
if(ql>qr) return;
if(lft==rgh) {
for(int i=ql;i<=qr;i++) ans[id[i]]=mat[lft].val;
return;
}
for(int i=lft;i<=mid;i++) T.update(mat[i].x,mat[i].y,1);
int c1=0,c2=0;
for(int i=ql;i<=qr;i++) {
int nw=id[i];
int sum=cur[nw]+count_black(q[nw]);
if(sum>=q[nw].k) t1[++c1]=nw;
else t2[++c2]=nw,cur[nw]=sum;
}
int qc=ql-1;
for(int i=1;i<=c1;i++) id[++qc]=t1[i];
for(int i=1;i<=c2;i++) id[++qc]=t2[i];
for(int i=lft;i<=mid;i++) T.update(mat[i].x,mat[i].y,-1);
solve(lft,mid,ql,ql+c1-1),solve(mid+1,rgh,ql+c1,qr);
}
int main() {
scanf("%d%d",&n,&m);
T.n=n;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
cm++;
mat[cm].x=i,mat[cm].y=j;
scanf("%d",&mat[cm].val);
}
}
sort(mat+1,mat+cm+1);
for(int i=1;i<=m;i++) {
scanf("%d%d%d%d%d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k);
id[i]=i;
}
solve(1,cm,1,m);
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
混合果汁
整体二分 + 线段树上二分
对于一个单独的询问,显然可以二分
对于所有美味度大于当前的二分值的果汁,贪心地选择便宜的,一直买到小朋友满意为止,然后看钱够不够
这个过程可以用线段树上的二分实现
建一棵权值线段树,下标是果汁的价格,每个节点维护价格在当前区间的所有果汁的总量和总价钱
每到一个节点,看左儿子的总量够不够,够就递归左边,不够就递归右边
多次询问可以考虑整体二分
每次调整权值线段树内维护的是从 left 到 middle 即可
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MX=100000,N=MX+10;
const int Inf=2e9;
int n,m;
struct Segtree {
#define ls (u<<1)
#define rs (u<<1|1)
struct Node {
ll sum,num;
}tr[N*4];
void pushup(int u) {
tr[u].num=tr[ls].num+tr[rs].num;
tr[u].sum=tr[ls].sum+tr[rs].sum;
}
void update(int u,int lft,int rgh,int p,int k) {
if(lft==rgh) {
tr[u].num+=k;
tr[u].sum=1ll*lft*tr[u].num;
return;
}
int mid=lft+rgh>>1;
if(p<=mid) update(ls,lft,mid,p,k);
else update(rs,mid+1,rgh,p,k);
pushup(u);
}
ll query(int u,int lft,int rgh,ll k) {
if(!k) return 0;
if(lft==rgh) return k*lft;
int mid=lft+rgh>>1;
if(tr[ls].num>=k) return query(ls,lft,mid,k);
return tr[ls].sum+query(rs,mid+1,rgh,k-tr[ls].num);
}
}T;
struct Juice {
int val,pri,lim;
friend bool operator < (Juice A,Juice B) {
return A.val>B.val;
}
}w[N];
struct Child {
int id;
ll csh,ltr;
}q[N],t1[N],t2[N];
int ans[N];
int now;
void solve(int lft,int rgh,int ql,int qr) {
if(ql>qr) return;
if(lft==rgh) {
for(int i=ql;i<=qr;i++) ans[q[i].id]=w[lft].val;
return;
}
int mid=lft+rgh>>1;
while(now<mid) now++,T.update(1,1,MX,w[now].pri,w[now].lim);
while(now>mid) T.update(1,1,MX,w[now].pri,-w[now].lim),now--;
int c1=0,c2=0;
for(int i=ql;i<=qr;i++) {
if(T.tr[1].num<q[i].ltr) t2[++c2]=q[i];
else if(T.query(1,1,MX,q[i].ltr)<=q[i].csh) t1[++c1]=q[i];
else t2[++c2]=q[i];
}
int qc=ql-1;
for(int i=1;i<=c1;i++) q[++qc]=t1[i];
for(int i=1;i<=c2;i++) q[++qc]=t2[i];
solve(lft,mid,ql,ql+c1-1),solve(mid+1,rgh,ql+c1,qr);
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d%d",&w[i].val,&w[i].pri,&w[i].lim);
for(int i=1;i<=m;i++) scanf("%lld%lld",&q[i].csh,&q[i].ltr),q[i].id=i;
w[++n]={-1,0,Inf};
sort(w+1,w+n+1);
solve(1,n,1,m);
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
整体二分总结
整体二分的模板:
void solve(int lft,int rgh,int ql,int qr) {
if(ql>qr) return; //当前区间没有询问
if(lft==rgh) { //找到答案
for(int i=ql;i<=qr;i++) //记录答案
return;
}
int mid=lft+rgh>>1;
//调整数据结构内维护的值为从 lft 到 mid
int c1=0,c2=0; //分成两部分
for(int i=ql;i<=qr;i++) {
//对于每一个询问进行 check
//并分成两部分
}
//把临时数组复制到查询的数组
//改变顺序,分成两拨
solve(lft,mid,ql,ql+c1-1),solve(mid+1,rgh,ql+c1,qr);//递归进行下一层二分
}
标签:二分,03,14,int,询问,mid,2024,include,ql
From: https://www.cnblogs.com/Orange-Star/p/18072325