P3212 HNOI2011 任务调度
题意
有 nnn 个任务,和两台机器 A,BA,BA,B,每个任务 iii 需要在 AAA 做 aia_iai 的时间,在 BBB 做 bib_ibi 的时间
任务有三种类型
1.\verb!1.!1. 类型 111
先在 AAA 做再去 BBB 做
2\verb!2!2 类型 222
先在 BBB 做再去 AAA 做
3.\verb!3.!3. 类型 333
可以任意归到类型 111 或类型 222
其中 n≤20n\leq 20n≤20,类型 333 的个数不超过 101010
赛时思路
考虑如果没有 333 怎么做
AAA 肯定先把所有 AAA 先的做了,BBB 同理
此时用时长的机器可以完美接上
而用时短的就需要具体考虑了
不妨令 AAA 机器是慢的,设 AAA 第一部分用了 ttt 单位时间
则对于 BBB 先的任务
若 bi≤ai,bi<=tb_i\leq a_i,b_i<=tbi≤ai,bi<=t
这种肯定放最前面做,做完后会产生新的时间优势 t′t't′,我们就可以用这个时间优势继续迭代
迭代完后所有任务要么 bi>tb_i>tbi>t 要么 ai>bia_i>b_iai>bi
于是我们将 >t>t>t 的放一组,≤t\leq t≤t 的放一组,
前面迭代的部分应该是没有问题的
剩下的考虑二分一个截至时间 TTT
算出 AAA 的起始时间 ststst
那么条件变为 ∀i,∑j=1ibj−aj<st\forall i,\sum_{j=1}^ib_j-a_j<st∀i,∑j=1ibj−aj<st
所以按 bj−ajb_j-a_jbj−aj 排序即可
发现前面的迭代是不需要的,放在后面一起做即可
而对于有 333 的情况,我们只需给每个 333 分配 111 或 222 即可
时间复杂度 O(2n×n)O(2^n\times n)O(2n×n)
题解
赛时思路已经基本没有问题了
只是最后式子列错了
应该是 ∑j=1ibj−aj+ai≤st\sum_{j=1}^ib_j-a_j+a_i\leq stj=1∑ibj−aj+ai≤st
二分 ststst ,然后贪心地往序列里塞能放的中 bj−ajb_j-a_jbj−aj 最小的
时间复杂度 O(2d×n2logn)O(2^d\times n^2logn)O(2d×n2logn),其中 ddd 为类型 333 的个数
代码
cpp
#include<bits/stdc++.h>
# define Maxn 25
using namespace std;
int n;
struct Node{int t,x,y;}a[Maxn];
vector<int> vec[5];
bool vis[Maxn];
int p[Maxn];
int ans=2e9+1;
bool cmp(int x,int y) {return 2*a[vec[2][x]].y-a[vec[2][x]].x<2*a[vec[2][y]].y-a[vec[2][y]].x;}
struct Sol{
void Solve() {
bool IfSwap=0;
int sum1=0,sum2=0;
for(int i=0;i<vec[1].size();i++) sum1+=a[vec[1][i]].x;
for(int i=0;i<vec[2].size();i++) sum2+=a[vec[2][i]].y;
if(sum1>sum2) {
IfSwap=1,swap(vec[1],vec[2]),swap(sum1,sum2);
for(int i=0;i<vec[1].size();i++) swap(a[vec[1][i]].x,a[vec[1][i]].y);
for(int i=0;i<vec[2].size();i++) swap(a[vec[2][i]].x,a[vec[2][i]].y);
}
// for(int i=0;i<vec[1].size();i++) printf("%d ",vec[1][i]);printf("\n");
// for(int i=0;i<vec[2].size();i++) printf("%d ",vec[2][i]);printf("\n");
// printf("%d %d\n",sum1,sum2);
for(int i=0;i<vec[1].size();i++) sum2+=a[vec[1][i]].y;
int l=0,r=2e9+1,mid,ST=0;
while(l<=r) {
mid=(l+r)>>1;
int Cnt=0,Sum=0;
for(int I=0;I<vec[2].size();I++) {
int Mn=-1;
for(int i=0;i<vec[2].size();i++) {
if((!vis[vec[2][i]])&&Sum+a[vec[2][i]].y<=mid) {
if(Mn==-1) Mn=vec[2][i];
else if(a[Mn].y-a[Mn].x>a[vec[2][i]].y-a[vec[2][i]].x) Mn=vec[2][i];
}
}
if(Mn==-1) break;
Cnt++,Sum+=a[Mn].y-a[Mn].x;
vis[Mn]=1;
}
for(int i=0;i<vec[2].size();i++) vis[vec[2][i]]=0;
if(Cnt==vec[2].size()) ST=mid,r=mid-1;
else l=mid+1;
}
ST=max(ST,sum1);
for(int i=0;i<vec[2].size();i++) ST+=a[vec[2][i]].x;
ans=min(ans,max(ST,sum2));
if(IfSwap) {
swap(vec[1],vec[2]);
for(int i=0;i<vec[1].size();i++) swap(a[vec[1][i]].x,a[vec[1][i]].y);
for(int i=0;i<vec[2].size();i++) swap(a[vec[2][i]].x,a[vec[2][i]].y);
}
while(!vec[1].empty()) {
int h=vec[1].back();
if(a[h].t==3) vec[1].pop_back();
else break;
}
while(!vec[2].empty()) {
int h=vec[2].back();
if(a[h].t==3) vec[2].pop_back();
else break;
}
}
}Sol;
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d%d%d",&a[i].t,&a[i].x,&a[i].y);
vec[a[i].t].push_back(i);
}
for(int s=0;s<(1<<vec[3].size());s++) {
for(int i=0;i<vec[3].size();i++) {
if((s>>i)&1) vec[1].push_back(vec[3][i]);
else vec[2].push_back(vec[3][i]);
}
Sol.Solve();
}
printf("%d\n",ans);
return 0;
}
P3965 TJOI2013 循环格
题意
一个矩阵,我们可以给其中每个格子定一个上下左右的方向
定义矩阵是一个循环格,当且仅当从每个格子出发,不断照着格子的方向移动,一定能回到那个出发的格子
特别的,如果走出边界,会循环到另一端
给出一个 n×mn\times mn×m 的矩阵,问最小修改几个格子能使其变成循环格
其中 n,m≤15n,m\leq 15n,m≤15
赛时思路 以及 题解
一个循环格等价于将矩阵分成若干回路
这等价于所有点的入度和出度均为 111
于是考虑网络流
让每个点选择它接下来走向哪里,原方向的费用为 000 ,其它方向的为 111
跑一遍费用流即可
代码
cpp
#include<bits/stdc++.h>
# define Maxn 18
# define inf 2e9+1
using namespace std;
int n,m,st,ed,a[Maxn][Maxn];
int pos[Maxn][Maxn],ans;
int head[2*Maxn*Maxn],tot=2;
int v[]={0,0,1,-1},s[]={1,-1,0,0};
char ch;
struct Node{int to,next,w,cost;}e[(Maxn*Maxn*6)*2];
void add(int u,int v,int w,int cost) {
e[tot]={v,head[u],w,cost};
head[u]=tot++;
}
void addedge(int u,int v,int w,int cost) {add(u,v,w,cost),add(v,u,0,-cost);}
int Pos(int i,int j,char ch) {
if(ch=='L') return pos[i][j-1?j-1:m];
if(ch=='R') return pos[i][j+1<=m?j+1:1];
if(ch=='U') return pos[i-1?i-1:n][j];
if(ch=='D') return pos[i+1<=n?i+1:1][j];
}
bool vis[2*Maxn*Maxn];
int dis[2*Maxn*Maxn],cur[2*Maxn*Maxn];
queue<int> q;
bool spfa() {
for(int i=1;i<=ed;i++) vis[i]=0,dis[i]=inf,cur[i]=head[i];
dis[st]=0,vis[st]=1,q.push(st);
while(!q.empty()) {
int h=q.front();q.pop(),vis[h]=0;
for(int i=head[h];i;i=e[i].next) {
if(e[i].w&&dis[e[i].to]>dis[h]+e[i].cost) {
dis[e[i].to]=dis[h]+e[i].cost;
if(!vis[e[i].to]) {
vis[e[i].to]=1;
q.push(e[i].to);
}
}
}
}return dis[ed]!=inf;
}
bool Vis[2*Maxn*Maxn];
int dfs(int rt,int flow) {
if(rt==ed) {ans+=dis[rt]*flow;return flow;};
Vis[rt]=1;
int res=0;
for(int i=cur[rt];i;i=e[i].next) {cur[rt]=i;
if((!Vis[e[i].to])&&e[i].w&&dis[e[i].to]==dis[rt]+e[i].cost) {
int nowflow=dfs(e[i].to,min(flow,e[i].w));
if(!nowflow) dis[e[i].to]=inf;
else {
e[i].w-=nowflow;
e[i^1].w+=nowflow;
flow-=nowflow;
res+=nowflow;
if(!flow) {Vis[rt]=0;return res;}
}
}
}Vis[rt]=0;return res;
}
int main() {
scanf("%d%d",&n,&m),st=2*n*m+1,ed=2*n*m+2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) pos[i][j]=(i-1)*m+j;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
cin>>ch;
addedge(st,pos[i][j],1,0);
addedge(pos[i][j]+n*m,ed,1,0);
if(Pos(i,j,'L')!=pos[i][j]) addedge(pos[i][j],Pos(i,j,'L')+n*m,1,ch!='L');
if(Pos(i,j,'R')!=pos[i][j]) addedge(pos[i][j],Pos(i,j,'R')+n*m,1,ch!='R');
if(Pos(i,j,'U')!=pos[i][j]) addedge(pos[i][j],Pos(i,j,'U')+n*m,1,ch!='U');
if(Pos(i,j,'D')!=pos[i][j]) addedge(pos[i][j],Pos(i,j,'D')+n*m,1,ch!='D');
}
}
while(spfa()) dfs(st,inf);
printf("%d\n",ans);
return 0;
}
/*
3 4
RRRD
URLL
LRRR
*/
P5232 JSOI2012 智者的考验
题意
对于一个 Rx×RyRx\times RyRx×Ry 的矩阵
有 Rx+RyR_x+R_yRx+Ry 个操作
每个操作将它对应的行或列异或 111
同时这个矩阵有一个目标状态,我们称它为 厄运星厄运星厄运星
给出一个长为 nnn 的操作区间,初始时都是 111
有 mmm 个操作,操作有三个类型
1.x,k\verb!1.! x,k1.x,k
将操作序列的第 xxx 个改成 kkk
2.l,r\verb!2.! l,r2.l,r
问顺着操作序列操作,l,rl,rl,r 的操作区间里会出现几个 厄运星厄运星厄运星
3.l,r,k\verb!3.! l,r,k3.l,r,k
将操作序列的 l,rl,rl,r 改成 kkk
其中 n≤106,m≤120000,Rx≤2,Ry≤3n\leq 10^6,m\leq120000,R_x\leq2,R_y\leq3n≤106,m≤120000,Rx≤2,Ry≤3
赛时思路
考虑利用线段树维护
每段区间我们记录一个对应关系 FFF
用 F(s)F(s)F(s) 表示 sss 通过当前区间后会变成什么
同时也记录 AnsAnsAns ,用 Ans(s)Ans(s)Ans(s) 表示 sss 通过区间会造成几个厄运星
于是区间修改就简单了,因为所有人都是同种操作,判断一下奇偶性即可
查询的话可以查两次,先查 1,l−11,l-11,l−1 获得初始状态,再查 l,rl,rl,r 获得答案
时间复杂度 O(nlogn×2Rx+Ry)O(nlogn\times2^{R_x+R_y})O(nlogn×2Rx+Ry)
题解
这题思路不难,问题在于空间
首先一个优化的点是叶子结点不用存
其次是我们将 存状态存状态存状态 改为 存哪些按钮按了存哪些按钮按了存哪些按钮按了 ,状态数缩到 252^525
我们进一步的还能发现按最后一个相当把前面的都按一遍,于是状态数缩到 242^424
此时空间就足以通过这道题了
代码
cpp
#include<bits/stdc++.h>
# define Maxn 1000005
# define Maxm 120005
# define pr pair<int,int>
# define fir first
# define sec second
using namespace std;
int n,m,op,x,l,r,k;
int Rx,Ry,S,Change[10];
struct Seg{
int lz[Maxn<<2];
int F[Maxn<<1][1<<4|1];
int Ans[Maxn<<1][1<<4|1];
int Get(int s,int x) {
if(x==Rx+Ry) s^=((1<<(Rx+Ry-1))-1);
else s^=(1<<(x-1));
return s;
}
int trans(int s) {
int res=0;
for(int i=0;i<Rx+Ry-1;i++)
if((s>>i)&1) res^=Change[i];
return res;
}
int Go(int rt,int l,int r,int s) {
if(l==r) return Get(s,lz[rt]);
else return F[rt][s];
}
int GetAns(int rt,int l,int r,int s) {
if(l==r) return (trans(Get(s,lz[rt]))==S);
else return Ans[rt][s];
}
void Tag(int rt,int l,int r,int v) {
if(l==r) lz[rt]=v;
else {
for(int s=0;s<(1<<(Rx+Ry-1));s++) {
int p=Get(s,v);
if((r-l+1)%2) {
F[rt][s]=p;
if(trans(s)==S) Ans[rt][s]=(r-l+1)/2;
else if(trans(p)==S) Ans[rt][s]=(r-l+2)/2;
else Ans[rt][s]=0;
}
else {
F[rt][s]=s;
if(trans(s)==S) Ans[rt][s]=(r-l+1)/2;
else if(trans(p)==S) Ans[rt][s]=(r-l+2)/2;
else Ans[rt][s]=0;
}
}
lz[rt]=v;
}
}
void Down(int rt,int l,int r,int mid) {
if(lz[rt]) {
Tag(rt<<1,l,mid,lz[rt]);
Tag(rt<<1|1,mid+1,r,lz[rt]);
lz[rt]=0;
}
}
void update(int rt,int l,int r,int L,int R,int v) {
if(L<=l&&r<=R) {
Tag(rt,l,r,v);
return ;
}
int mid=(l+r)>>1;Down(rt,l,r,mid);
if(L<=mid) update(rt<<1,l,mid,L,R,v);
if(R>mid) update(rt<<1|1,mid+1,r,L,R,v);
for(int s=0;s<(1<<(Rx+Ry-1));s++) {
F[rt][s]=Go(rt<<1|1,mid+1,r,Go(rt<<1,l,mid,s));
Ans[rt][s]=GetAns(rt<<1,l,mid,s)+GetAns(rt<<1|1,mid+1,r,Go(rt<<1,l,mid,s));
}
}
pr query(int rt,int l,int r,int L,int R,int s) {
if(!R) return {s,0};
if(L<=l&&r<=R) {
if(l==r) {
int bz=Get(s,lz[rt]);
return {bz,(trans(bz)==S)};
}
else return {F[rt][s],Ans[rt][s]};
}
int mid=(l+r)>>1;Down(rt,l,r,mid);
if(R<=mid) return query(rt<<1,l,mid,L,R,s);
if(L>mid) return query(rt<<1|1,mid+1,r,L,R,s);
pr res1=query(rt<<1,l,mid,L,R,s);
pr res2=query(rt<<1|1,mid+1,r,L,R,res1.fir);
return {res2.fir,res1.sec+res2.sec};
}
}Seg;
int main() {
scanf("%d%d",&Rx,&Ry);
for(int i=1;i<=Rx;i++) {
for(int j=1;j<=Ry;j++)
scanf("%d",&x),S=S*2+x;
}
for(int i=1;i<Rx+Ry;i++) {
if(i<=Rx) {
for(int j=1;j<=Ry;j++) {
int p=Rx*Ry-((i-1)*Ry+j);
Change[i-1]^=(1<<p);
}
}
else {
for(int j=1;j<=Rx;j++) {
int p=Rx*Ry-((j-1)*Ry+(i-Rx));
Change[i-1]^=(1<<p);
}
}
// printf("%d ",Change[i-1]);
}
// printf("\n");
scanf("%d%d",&n,&m),Seg.update(1,1,n,1,n,1);
for(int i=1;i<=m;i++) {
scanf("%d",&op);
if(op==0) {
scanf("%d%d",&x,&k);
Seg.update(1,1,n,x,x,k);
// printf("%d\n",Seg.trans(Seg.F[1][0]));
}
if(op==1) {
scanf("%d%d",&l,&r);
pr res=Seg.query(1,1,n,1,l-1,0);
printf("%d\n",Seg.query(1,1,n,l,r,res.fir).sec);
}
if(op==2) {
scanf("%d%d%d",&l,&r,&k);
Seg.update(1,1,n,l,r,k);
}
}
return 0;
}