[JSOI2010 缓存交换]贪心、二叉堆

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1826
【算法分析】
如果一个x已经在缓存中,那么本次x进入就不需要再被计算。
也就是说,无论弄谁,本质上都是一样的。
而且当前这个点,必须被push进缓存。
容易得到一个贪心解法:
设当前值为x,就是按下一个x的位置前后排个序里,越后的越废。
于是只取前m个就可以了。于是可以用个二叉堆优化。
【时间复杂度】O(n lg n)
【其它】2Y。一开始把堆得del函数写错了呃@.@,因为删的不一定是第一个,写不习惯了。。
【CODE】
#include using namespace std;
const int N=105555;
struct edge{int y,next,pos;}g[N];
int n,m,e;
int a[N],lx[N],ls[N],Next[N],zz[N];

struct Heap_t{
int tot;
struct Node{int key,pos;}h[N];
void Swap(int i,int j){
Node tmp=h[i]; h[i]=h[j]; h[j]=tmp;
zz[h[i].pos]=i;
zz[h[j].pos]=j;
}
void up(int k){
while (k>1 && h[k].key>h[k/2].key){
Swap(k,k/2);
k/=2;
}
}

void down(int p){
int k;
while (p*2<=tot){
k=p*2;
if (k if (h[k].key>h[p].key){
Swap(k,p);
p=k;
}else break;
}
}

void ins(int key,int pos){
tot++;
h[tot].key=key;
h[tot].pos=pos;
zz[pos]=tot;
up(tot);
}

void del(int p){
Swap(p,tot);
zz[h[tot].pos]=0;
tot–;
up(p);
down(p);
}
}heap;

int Find(int x){
int l=1,r=n,mid;
while (l mid=(l+r)/2;
if (x>lx[mid]) l=mid+1;
else r=mid;
}
return l;
}

void init(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
lx[i]=a[i];
}
sort(lx+1,lx+n+1);
for (int i=1;i<=n;i++) a[i]=Find(a[i]);
for (int i=1;i<=n;i++) ls[i]=n+1;
for (int i=n;i>=1;i–){
Next[i]=ls[a[i]];
ls[a[i]]=i;
}
}

void work(){
memset(zz,0,sizeof(zz));
heap.tot=0;
int i,ans=0;
for (i=1;i<=n;i++){
if (zz[a[i]]==0){
ans++;
if (heap.tot==m) heap.del(1);
heap.ins(Next[i],a[i]);
}
else{
heap.del(zz[a[i]]);
heap.ins(Next[i],a[i]);
}
}
printf("%dn",ans);
}

int main(){
init();
work();
}

[HAOI2007 理想的正方形]单调队列

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1047
【算法分析】
先弄一个dmax[i][j]和dmin[i][j]数组分别表示(i,j)这个点往下的n(是题目中的n)个点中,最大值(最小值)是多少。然后这样压缩以后横向再来一次就可以。
【其它】
看到群里说2维单调队列,然后师傅就发了这个链接。。。
难道这就是传说中的2维
还以为是很恐怖的东西。。。
【其它】1Y。
【CODE】
#include #include #include #include using namespace std;
const int N=1005;
int m,n,L,i,j,head,tail;
int w[N][N],dmax[N][N],dmin[N][N],a[N][N],b[N][N],qp[N],qf[N];

int main(){
scanf("%d%d%d",&m,&n,&L);
for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
scanf("%d",&w[i][j]);
for (j=1;j<=n;j++){
head=1; tail=0;
for (i=1;i while (head<=tail && w[i][j]>qf[tail]) tail--;
qf[++tail]=w[i][j]; qp[tail]=i;
}
for (i=1;i+L-1<=m;i++){
while (head<=tail && w[i+L-1][j]>qf[tail]) tail--;
qf[++tail]=w[i+L-1][j]; qp[tail]=i+L-1;
while (qp[head] dmax[i][j]=qf[head];
}
}

for (j=1;j<=n;j++){
head=1; tail=0;
for (i=1;i while (head<=tail && w[i][j] qf[++tail]=w[i][j]; qp[tail]=i;
}
for (i=1;i+L-1<=m;i++){
while (head<=tail && w[i+L-1][j] qf[++tail]=w[i+L-1][j]; qp[tail]=i+L-1;
while (qp[head] dmin[i][j]=qf[head];
}
}
int ans=0x7FFFFFFF;
for (i=1;i+L-1<=m;i++){
head=1; tail=0;
for (j=1;j<=n;j++){
while (head<=tail && dmax[i][j]>qf[tail]) tail--;
qf[++tail]=dmax[i][j]; qp[tail]=j;
while (qp[head]<=j-L) head++;
a[i][j]=qf[head];
}
head=1; tail=0;
for (j=1;j<=n;j++){
while (head<=tail && dmin[i][j] qf[++tail]=dmin[i][j]; qp[tail]=j;
while (qp[head]<=j-L) head++;
b[i][j]=qf[head];
}
}
for (i=1;i+L-1<=m;i++)
for (j=L;j<=n;j++)
ans=min(ans,a[i][j]-b[i][j]);
printf("%dn",ans);
}

[HDOJ1512 Monkey King]可合并堆

【算法分析】
据Lost天牛的所有算法总结所说,现在一般人能写得起有两种可合并堆,一种是左偏树,一种是斜堆。
然后斜堆好像算法是平摊的还是啥,单次可能变成O(n)@.@。。。
如果不是平摊只是期望的话。。。那么估计想二叉检索树一样对某些特殊数据会悲剧啊。。。
还是老老实实写左偏树比较好。
然后这题就是左偏树的模板题。
当然还要搞个并查集。
【其它】第一个左偏树。1Y
【CODE】
#include #include #include const int N=105555;
int n,m,fa[N];
struct Node{Node *l,*r;int key,dis;}sp[N],*root[N],*null;

void input(){
null=&sp[0];
for (int i=1;i<=n;i++){
scanf("%d",&sp[i].key);
fa[i]=i;
root[i]=&sp[i];
root[i]->dis=0;
root[i]->l=root[i]->r=null;
}
}

inline void swap(Node *&A,Node *&B){Node *tmp=A; A=B; B=tmp;}

int gf(int k){
if (fa[k]==k) return k;
else return fa[k]=gf(fa[k]);
}

Node* Union(Node *p,Node *q){
if (p==null) return q;
if (q==null) return p;
if (p->key p->r=Union(p->r,q);
if (p->l->dis if (p->r==null) p->dis=0;
else p->dis=p->r->dis+1;
return p;
}

void myself_c(Node *&R){
Node *lc=R->l,*rc=R->r;
R->key>>=1; R->l=R->r=null;
R=Union(R,lc);
R=Union(R,rc);
}

void work(){
scanf("%d",&m);
for (int x,y,i=1;i<=m;i++){
scanf("%d%d",&x,&y);
if (gf(x)==gf(y)) printf("-1n");
else{
myself_c(root[fa[x]]);
myself_c(root[fa[y]]);
root[fa[y]]=Union(root[fa[x]],root[fa[y]]);
fa[fa[x]]=fa[y];
printf("%dn",root[fa[y]]->key);
}
}
}

int main(){
while (scanf("%d",&n)!=EOF){
input();
work();
}
}

[JSOI2010 满汉全席]2-sat

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1823
【算法分析】
对于每种材料,有做成满式和汉式两种情况,然后两者取且必须取一种。
而且约束条件是,(A做成XX式 or B做成yy式) = true。
逻辑运算符。。。很容易想到2-sat。
回想2-sat的建图,一条边(A,B)的意义是:如果你选了A,那么就必须选B。
恩,这个题目的逻辑式子转化成:加入你选了A的对立面,那么一定要选B啊。
恩,就这样。。。
【其它】很久没2-sat了,1Y。
【CODE】
#include #include #include const int N=1000;
const int E=1000000;
int n,m,e,times,ct,tot;
int lx[N],ly[N];
int fa[N],order[N];
bool v[N],Type;

struct Graph_t{
struct edge{int x,y;edge *next;}g[E],*ls[N];
void clr(){e=0; memset(ls,0,sizeof(ls));}
void addedge(int x,int y){
g[++e].x=x; g[e].y=y; g[e].next=ls[x]; ls[x]=&g[e];
}

void op(){
int tmp=e;
clr();
for (int i=1;i<=tmp;i++) addedge(g[i].y,g[i].x);
}

void dfs(int p){
v[p]=true;
for (edge *t=ls[p];t;t=t->next)
if (!v[t->y]) dfs(t->y);
if (Type) fa[p]=ct;
else order[++tot]=p;
}
}G;

int Get(){
char ch[20];
int x;
scanf("%s",ch);
sscanf(ch+1,"%d",&x);
if (ch[0]==’h’) x+=n;
return x;
}

void init(){
int i;
scanf("%d%d",&n,&m);
for (i=1;i<=m;i++){
lx[i]=Get();
ly[i]=Get();
}
}

void build(){
G.clr();
int i,j;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++){
if (lx[j]==i+n) G.addedge(i,ly[j]);
if (ly[j]==i+n) G.addedge(i,lx[j]);
}
for (i=n+1;i<=n*2;i++)
for (j=1;j<=m;j++){
if (lx[j]==i-n) G.addedge(i,ly[j]);
if (ly[j]==i-n) G.addedge(i,lx[j]);
}
}

void SCC(){
memset(v,false,sizeof(v));
tot=0;
Type=false;
for (int i=1;i<=2*n;i++)
if (!v[i]) G.dfs(i);
Type=true;
G.op();
memset(v,false,sizeof(v));
for (int i=tot;i>=1;i–)
if (!v[order[i]]){
ct=order[i];
G.dfs(order[i]);
}
}

void output(){
bool ans=true;
for (int i=1;i<=n;i++)
if (fa[i]==fa[i+n]) ans=false;
if (ans) puts("GOOD");
else puts("BAD");
}

int main(){
int Tc,i;
scanf("%d",&Tc);
for (i=1;i<=Tc;i++){
init();
build();
SCC();
output();
}
}

[ZJOI2010 base 基站选址]线段树优化的动态规划★★

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1835
【算法分析】
首先第一直觉就能写出一个状态和方程。
F[i][j]表示第i个地点建站,然后前i个村庄一共建了j个站时的前i个村庄需要的最小费用。
于是顺理成章的方程:
F[i][j]=min(F[l][j-1]+w[l][i])+c[i]
其中w[l][i]是这样定义的:
    从l+1到i-1这些点p里,所有满足d[p]+s[p]当然,这还是其次,最重要的是我们可能可以对每一层建立一个数据结构降低转移的复杂度。

让我们来观察假设i+1以后,方程里的东西有什么变化。

F[l][j-1]和c[i]显然不变。那么变得只是w[l][i]。
他怎么变呢?
围观一下上面红色的w[l][i]的定义,容易发现,唯一边的就是:
d[p]+s[p]而与它的w有关的转移范围显然是所有满足d[j](具体求那个范围可以在一开始就二分出来。然后用一个邻接表起结束位置的点)
【时间复杂度】O(nk lg n)
【其它】2Y。第一次错是因为看错了一点题目,题目要求是<=k个站,而不是必须建k个站。。。
ZJOI2010 Day1 Finished。
【CODE】
#include #include #include const int N=25555;
const int INF=1000000005;
int n,k;
int pF[N],F[N],c[N],d[N],s[N],w[N],st[N],ed[N];
inline int min(int x,int y){return xstruct Segment_Type{
struct Node{int l,r,key,delta;}Tr[N*6];

void pushdown(int p){
if (!Tr[p].delta) return;
if (Tr[p].l!=Tr[p].r){
Tr[p<<1].delta+=Tr[p].delta;
Tr[(p<<1)+1].delta+=Tr[p].delta;
}
Tr[p].key+=Tr[p].delta;
Tr[p].delta=0;
}

void update(int p){
pushdown(p<<1);
pushdown((p<<1)^1);
Tr[p].key=min(Tr[p<<1].key,Tr[(p<<1)^1].key);
}

void build(int p,int l,int r){
Tr[p].l=l; Tr[p].r=r; Tr[p].delta=0;
if (l==r){
Tr[p].key=pF[l];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build((p<<1)^1,mid+1,r);
update(p);
}

void add(int p,int l,int r,const int delta){
if (l>r) return;
if (l<=Tr[p].l && Tr[p].r<=r)
Tr[p].delta+=delta;
else{
pushdown(p);
int mid=(Tr[p].l+Tr[p].r) >> 1;
if (l<=mid) add(p<<1,l,r,delta);
if (r> mid) add((p<<1)^1,l,r,delta);
update(p);
}
}

int solve(int p,int l,int r){
if (l>r || r pushdown(p);
if (l<=Tr[p].l && Tr[p].r<=r) return Tr[p].key;
return min(solve(p<<1,l,r),solve((p<<1)^1,l,r));
}
}Segment_Tree;

struct edge{int x,y;edge *next;};
struct Lint_t{
edge g[N],*ls[N];
int e;
void init(){e=0;memset(ls,0,sizeof(ls));}
void add(int x,int y){g[++e].x=x; g[e].y=y; g[e].next=ls[x]; ls[x]=&g[e];}
}Ed_Link;

int Findl(int x){
int l=1,r=n,mid;
while (l mid=(l+r)>>1;
if (x<=d[mid]) r=mid;
else l=mid+1;
}
return l;
}

int Findr(int x){
int l=1,r=n,mid;
while (l+1 mid=(l+r)>>1;
if (d[mid]<=x) l=mid;
else r=mid-1;
}
if (d[r]<=x) l=r;
return l;
}

void input(){
scanf("%d%d",&n,&k);
int i;
for (i=2;i<=n;i++) scanf("%d",&d[i]);
for (i=1;i<=n;i++) scanf("%d",&c[i]);
for (i=1;i<=n;i++) scanf("%d",&s[i]);
for (i=1;i<=n;i++) scanf("%d",&w[i]);
d[n+1]=d[n]+INF; c[n+1]=w[n+1]=s[i]=0;
n++;
Ed_Link.init();
for (i=1;i<=n;i++){
st[i]=Findl(d[i]-s[i]);
ed[i]=Findr(d[i]+s[i]);
Ed_Link.add(ed[i],i);
}
}

void dp(){
if (k==0){
int Sum=0;
for (int i=1;i<=n;i++) Sum+=w[i];
printf("%dn",Sum);
return;
}
int i,j,Sum=0,ans=INF;
for (i=1;i<=n;i++){
F[i]=Sum+c[i];
for (edge *t=Ed_Link.ls[i];t;t=t->next)
Sum+=w[t->y];
}
for (j=1;j<=k;j++){
ans=min(ans,F[n]);
memcpy(pF,F,sizeof(F));
for (i=1;i<=n;i++) F[i]=INF;
Segment_Tree.build(1,1,n);
for (i=1;i<=n;i++){
F[i]=min(F[i],Segment_Tree.solve(1,1,i-1)+c[i]);
for (edge *t=Ed_Link.ls[i];t;t=t->next)
Segment_Tree.add(1,1,st[t->y]-1,w[t->y]);
}
}
ans=min(ans,F[n]);
printf("%dn",ans);
}

int main(){
input();
dp();
}