原题见这里(BZOJ和BSOJ都挂了,真杯具,只能借助RQ了囧……)

难度不是很大,就是特殊情况比较多,比较猥琐(不过本题数据弱,就算不考虑所有的特殊情况也能过7个点)。
首先O(NM)的朴素算法很好想到:枚举K,然后给每个结点编号即可。在编号时,先随便指定一个未编号的点,设它的编号为0,然后遍历所有和它相关联的边(这里可以把原图想象成一个无向图),将这些边的另一个端点编上号即可,中间若某个点的编号出现矛盾,则这个K不合法,否则这个K合法。
然后进行优化:合法的K实际上是有限制的,它必然是某个数的因数,具体来说,对这个图进行DFS,并考察其中所有的跨越边和逆向边,对于跨越边<i, j>,设遍历树中i、j间距离为D,则合法的K必然是(D-1)的因数(因为i和遍历树中j的父结点都有指向j的边,它们的编号应相同,而它们之间的距离为(D-1));对于逆向边<i, j>,设遍历树中i、j间距离为D',则合法的K必然是(D'+1)的因数(因为这里形成了一个环,环的长度为(D'+1))。这样一来就明显缩小了K的取值范围,再进行枚举,就可以显著缩短时间。

下面是一些极其猥琐的特殊情况:
(1)根据题意,必须是K类每类都有,因此在尝试编号成功(没有发生任何矛盾)后,还要看一下实际出现的编号数目是否等于K,若小于K,同样不合法;
(2)该图的基图可能不连通,此时对于其基图的每个连通块,其编号互不影响,所以要对每个连通块分别统计实际出现的编号数目,设它们的和为SUM,则不大于SUM的K值均合法(只要中间不出现矛盾),因此可以直接得到最大值为SUM,提前结束;不过,这种特判只有在总共实际出现的编号数目小于K的情况下才能进行;
(3)由于考察的是实际出现的编号数目,因此最后求出的最大值、最小值可能小于3,这时应作出如下处理:若最大值小于3,则无解;若最小值小于3,则将最小值改为3。

本题比较猥琐的数据是第4、5、6个点,分别出现了上述的第(1)、(2)、(3)种特殊情况,此外,这三个点建出的图中竟然没有一条跨越边或逆向边!

代码:
#include <iostream>
#include 
<stdio.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
const int MAXN = 100000, MAXM = 1100001;
struct edge {
    
int a, b, s, pre, next;
} E0[MAXM], E[MAXM 
+ MAXM];
int n, m0, m, P, len, X[MAXN], No[MAXN], stk[MAXN], st[MAXN], dep[MAXN], V[MAXN], fo[MAXN], Q[MAXN], res0, res1;
bool vst[MAXN], T0[MAXN];
long long T[MAXN], _Z = 0;
void init_d()
{
    re(i, n) E[i].a 
= E[i].pre = E[i].next = E0[i].a = E0[i].pre = E0[i].next = i;
    m0 
= n; if (n % 2) m = n + 1else m = n;
}
void add_edge(int a, int b)
{
    E0[m0].a 
= a; E0[m0].b = b; E0[m0].pre = E0[a].pre; E0[m0].next = a; E0[a].pre = m0; E0[E0[m0].pre].next = m0++;
    E[m].a 
= a; E[m].b = b; E[m].s = 1; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m++;
    E[m].a 
= b; E[m].b = a; E[m].s = -1; E[m].pre = E[b].pre; E[m].next = b; E[b].pre = m; E[E[m].pre].next = m++;
}
void init()
{
    freopen(
"party.in""r", stdin);
    
int _m, a, b; scanf("%d%d"&n, &_m); init_d();
    re(i, _m) {
        scanf(
"%d%d"&a, &b);
        add_edge(
--a, --b);
    }
    fclose(stdin);
}
int gcd(int a, int b)
{
    
int r;
    
while (b) {
        r 
= a % b; a = b; b = r;
    }
    
return a;
}
void prepare()
{
    
int tp, x, y, ord = 0;
    
bool fd;
    re(i, n) V[i] 
= 0; P = 0;
    re(i, n) 
if (!V[i]) {
        stk[tp 
= 0= i; fo[i] = ord++; V[i] = 1; st[i] = E0[i].next; dep[i] = 0;
        
while (tp >= 0) {
            x 
= stk[tp]; fd = 0;
            
for (int p=st[x]; p != x; p=E0[p].next) {
                y 
= E0[p].b;
                
if (!V[y]) {
                    stk[
++tp] = y; fo[y] = ord++; V[y] = 1; st[y] = E0[y].next; dep[y] = dep[x] + 1; st[x] = E0[p].next; fd = 1break;
                } 
else if (V[y] == 1) P = gcd(P, dep[x] - dep[y] + 1); else if (fo[y] > fo[x]) P = gcd(P, dep[y] - dep[x] - 1);
            }
            
if (!fd) {V[x] = 2; tp--;}
        }
    }
    len 
= 0; re3(i, 3, n) if (!(P % i)) X[len++= i;
}
int test(int K)
{
    re(i, n) {vst[i] 
= 0; No[i] = -1;}
    re(i, K) T0[i] 
= 0;
    
int x, y, No0, sum = 0, sum0 = 0;
    re(i, n) 
if (!vst[i]) {
        No[i] 
= 0; Q[0= i; vst[i] = 1; _Z++if (T[0!= _Z) {T[0= _Z; sum++;} if (!T0[0]) {T0[0= 1; sum0++;}
        
for (int front=0, rear=0; front<=rear; front++) {
            x 
= Q[front];
            
for (int p=E[x].next; p != x; p=E[p].next) {
                y 
= E[p].b; No0 = No[x] + E[p].s;
                
if (No0 == K) No0 = 0else if (No0 == -1) No0 = K - 1;
                
if (No[y] >= 0 && No0 != No[y]) return -1else {
                    No[y] 
= No0;
                    
if (T[No0] != _Z) {T[No0] = _Z; sum++;}
                    
if (!T0[No0]) {T0[No0] = 1; sum0++;}
                }
                
if (!vst[y]) {vst[y] = 1; Q[++rear] = y;}
            }
        }
    }
    
if (sum0 < K) res0 = sum;
    
return sum0;
}
void solve()
{
    
int K, K0; res0 = res1 = -1;
    re(i, len) {
        K 
= X[i]; K0 = test(K);
        
if (K0 != -1) {
            
if (res1 == -1) res1 = K0;
            
if (K0 < K) breakelse res0 = K;
        }
    }
    
if (res0 < 3) res0 = res1 = -1else if (res1 < 3) res1 = 3;
}
void pri()
{
    freopen(
"party.out""w", stdout);
    printf(
"%d %d\n", res0, res1);
    fclose(stdout);
}
int main()
{
    init();
    prepare();
    solve();
    pri();
    
return 0;
}

posted @ 2011-07-14 12:23 Mato_No1 阅读(454) | 评论 (0)编辑 收藏

世界上已经木有比我还弱的了……

256:这么水的题竟然搞了30+min,一开始米看见每隔7天就要买同一种,以为是DP,结果测Sample3米过去,于是重写……(要木有Sample3就杯具了)

512:由于256上卡了太多时间,这题在快写完的时候到时间了……

1024:看都米看……

结果:全场400+名……估计下次得掉回DIV2了……

posted @ 2011-07-14 10:04 Mato_No1 阅读(536) | 评论 (3)编辑 收藏

     摘要: 【2-SAT问题】现有一个由N个布尔值组成的序列A,给出一些限制关系,比如A[x] AND A[y]=0、A[x] OR A[y] OR A[z]=1等,要确定A[0..N-1]的值,使得其满足所有限制关系。这个称为SAT问题,特别的,若每种限制关系中最多只对两个元素进行限制,则称为2-SAT问题。由于在2-SAT问题中,最多只对两个元素进行限制,所以可能的限制关系共...  阅读全文

posted @ 2011-07-13 11:51 Mato_No1 阅读(8653) | 评论 (1)编辑 收藏

【原题见这里

本沙茶见过的最猥琐的DP题啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊……

设F[i]为将A[1..i]拆分成若干段的最大值最小和,则有
F[i]=min{F[j] + max[j+1, i]}(B[i]<=j<i),其中max[j+1, i]表示A[j+1..i]中的最大值,B[i]表示从i向左最远可以延伸到哪里(也就是满足SUM[x..i]<=m的最小的x值)。B数组可以通过预处理在O(N)时间内得到。
边界:F[0]=0。

下面是优化过程。JZP神犇的论文里面已经够详细了。这里只是简要说明一下。
首先容易证明,F是单调递增的。
然后一个很关键的定理是:若F[i]的最优决策为j,则有A[j]>∀A[k](j<k<=i)。
证明:用反证法。若A[j+1..i]中存在不小于A[j]的值,则可得max[j..i]=max[j+1..i],又因为F单调递增,所以F[j-1]+max[j..i]<=F[j]+max[j+1.i],即决策(j-1)一定不比决策j差,也就是决策j不可能成为最优决策。
这样,可以维护一个下标严格递增、A值严格递减的队列Q(即对于队列中的任意两个元素Q[i]和Q[j],若i<j,则Q[i].pos<Q[j].pos且A[Q[i].pos]>A[Q[j].pos],具体实现时pos可省略)。则可能成为最优决策的决策要么是在这个队列Q里,要么是B[i]。对于队列中的某个决策Q[x],该决策导出的值为F[Q[x]]+A[Q[x + 1]](很容易证明max[Q[x]+1..i]=A[Q[x + 1]]),找到这些导出的值中的最小值即可(注意,队尾元素没有导出值)。对于决策B[i],只需要在预处理的时候同时得到MAX[i]=max[B[i]+1..i]即可(也可以在O(N)时间内得到),决策B[i]导出的值为F[B[i]]+MAX[i]。
在删除队首过时元素的时候,需要把导出值也删除,删除队尾元素也一样,插入的时候,若插入前队列不为空,则需要插入一个导出值。也就是,需要一个支持在对数时间内进行插入、删除任意结点、找最小值等操作,显然用平衡树最好。

注意事项:
(1)不管是在队首删除还是在队尾删除,若删除的是队列中的最后一个元素,则不需要在平衡树中删除导出值;
(2)插入时,若插入前队列为空,则不需要在平衡树中插入导出值;
(3)在计算F[i]时,应先将决策i压入。

代码:
#include <iostream>
#include 
<stdio.h>
using namespace std;
#define re1(i, n) for (int i=1; i<=n; i++)
const int MAXN = 100001;
struct node {
    
int l, r, p, sz0, sz, mul;
    
long long v;
} T[MAXN];
const long long INF = ~0Ull >> 2;
int n, N = 0, a[MAXN], b[MAXN], MAX[MAXN], Q[MAXN], front = 0, rear = -1, root = 0;
long long m, F[MAXN], res = 0;
void init()
{
    cin 
>> n >> m;
    re1(i, n) scanf(
"%d"&a[i]); a[0= ~0U >> 2;
}
void prepare()
{
    re1(i, n) 
if (a[i] > m) {res = -1return;}
    
int x = 1;
    
long long sum = 0;
    re1(i, n) {
        
for (sum+=a[i]; sum>m; sum-=a[x++]) ;
        b[i] 
= x - 1;
    }
    re1(i, n) {
        
for (; front<=rear && Q[front]<=b[i]; front++) ;
        
for (; front<=rear && a[Q[rear]]<=a[i]; rear--) ;
        Q[
++rear] = i; MAX[i] = a[Q[front]];
    }
}
void vst(int x)
{
    
if (x) {
        cout 
<< T[x].v << ' ';
        vst(T[x].l); vst(T[x].r);
    }
}
void slc(int _p, int _c)
{
    T[_p].l 
= _c; T[_c].p = _p;
}
void src(int _p, int _c)
{
    T[_p].r 
= _c; T[_c].p = _p;
}
void upd(int x)
{
    T[x].sz0 
= T[T[x].l].sz0 + T[T[x].r].sz0 + T[x].mul;
    T[x].sz 
= T[T[x].l].sz + T[T[x].r].sz + 1;
}
void lrot(int x)
{
    
int y = T[x].p;
    
if (y == root) T[root = x].p = 0else {int p = T[y].p; if (y == T[p].l) slc(p, x); else src(p, x);}
    src(y, T[x].l); slc(x, y); T[x].sz0 
= T[y].sz0; T[x].sz = T[y].sz; upd(y);
}
void rrot(int x)
{
    
int y = T[x].p;
    
if (y == root) T[root = x].p = 0else {int p = T[y].p; if (y == T[p].l) slc(p, x); else src(p, x);}
    slc(y, T[x].r); src(x, y); T[x].sz0 
= T[y].sz0; T[x].sz = T[y].sz; upd(y);
}
void maintain(int x, bool ff)
{
    
int z;
    
if (ff) {
        
if (T[T[T[x].r].r].sz > T[T[x].l].sz) {z = T[x].r; lrot(z);}
        
else if (T[T[T[x].r].l].sz > T[T[x].l].sz) {z = T[T[x].r].l; rrot(z); lrot(z);} else return;
    } 
else {
        
if (T[T[T[x].l].l].sz > T[T[x].r].sz) {z = T[x].l; rrot(z);}
        
else if (T[T[T[x].l].r].sz > T[T[x].r].sz) {z = T[T[x].l].r; lrot(z); rrot(z);} else return;
    }
    maintain(T[z].l, 
0); maintain(T[z].r, 1); maintain(z, 0); maintain(z, 1);
}
int find(long long _v)
{
    
int i = root;
    
long long v0;
    
while (i) {
        v0 
= T[i].v;
        
if (_v == v0) return i; else if (_v < v0) i = T[i].l; else i = T[i].r;
    }
    
return 0;
}
int Find_Kth(int K)
{
    
int i = root, s0, m0;
    
while (1) {
        s0 
= T[T[i].l].sz0; m0 = T[i].mul;
        
if (K <= s0) i = T[i].l; else if (K <= s0 + m0) return i; else {K -= s0 + m0; i = T[i].r;}
    }
}
void ins(long long _v)
{
    
if (!root) {
        T[
++N].v = _v; T[N].l = T[N].r = T[N].p = 0; T[N].sz0 = T[N].sz = T[N].mul = 1; root = N;
    } 
else {
        
int i = root, j;
        
long long v0;
        
while (1) {
            T[i].sz0
++; v0 = T[i].v;
            
if (_v == v0) {T[i].mul++return;} else if (_v < v0) j = T[i].l; else j = T[i].r;
            
if (j) i = j; else break;
        }
        T[
++N].v = _v; T[N].l = T[N].r = 0; T[N].sz0 = T[N].sz = T[N].mul = 1if (_v < v0) slc(i, N); else src(i, N);
        
while (i) {T[i].sz++; maintain(i, _v > T[i].v); i = T[i].p;}
    }
}
void del(int x)
{
    
if (T[x].mul > 1) {
        T[x].mul
--while (x) {T[x].sz0--; x = T[x].p;}
    } 
else {
        
int l = T[x].l, r = T[x].r, p;
        
if (l && r) {
            
int y; while (y = T[l].r) l = y;
            T[x].v 
= T[l].v; T[x].mul = T[l].mul; p = T[l].p;
            
if (l == T[p].l) slc(p, T[l].l); else src(p, T[l].l);
            
while (p) {upd(p); p = T[p].p;}
        } 
else {
            
if (x == root) T[root = l + r].p = 0else {p = T[x].p; if (x == T[p].l) slc(p, l + r); else src(p, l + r); while(p) {upd(p); p = T[p].p;}}
        }
    }
}
long long h(int x)
{
    
return F[Q[x]] + a[Q[x + 1]];
}
void solve()
{
    F[
0= 0; front = 0; rear = 0; Q[0= 0;
    re1(i, n) {
        
for (; front<=rear && Q[front]<b[i];) {if (front < rear) del(find(h(front))); front++;}
        
for (; front<=rear && a[Q[rear]]<=a[i];) {if (front < rear) del(find(h(rear - 1))); rear--;}
        Q[
++rear] = i; if (front < rear) ins(h(rear - 1));
        
if (root) F[i] = T[Find_Kth(1)].v; else F[i] = INF;
        
if (F[b[i]] + MAX[i] < F[i]) F[i] = F[b[i]] + MAX[i];
    }
    res 
= F[n];
}
void pri()
{
    cout 
<< res << endl;
}
int main()
{
    init();
    prepare();
    
if (!res) solve();
    pri();
    
return 0;
}

posted @ 2011-07-08 12:40 Mato_No1 阅读(483) | 评论 (1)编辑 收藏

多重背包问题朴素时间复杂度为O(NMS)(这里S是所有物品的数量s之和),经过二进制优化后时间复杂度为O(NMlog2S),这个复杂度已经能够应付大多数题了,但对于某些特别卡时间的题(比如N*M=107的),仍然会TLE。这时,可以用单调队列优化,时间复杂度降为O(NM)。

首先看一下多重背包问题的朴素转移方程:
F[i][j] = max{F[i-1][j-x*w[i]]+x*v[i]} (0<=x<=s[i], j>=x*w[i])
如果使用滚动数组,忽略i这一维,设w0=w[i],v0=v[i],s0=s[i],得:
F[j] = max{F[j-x*w0]+x*v0} (0<=x<=s0, j>=x*w0)
看上去这和单调队列木有神马关系,因为决策下标(j-x*w0)不是一个整数区间,中间是有间隔的。然而可以发现,这个方程的限制条件“0<=x<=s0,j>=x*w0”,也就是x的下界是max{0, j/w0(下取整)},当j单调递增时,这个下界也是单调递增的。这满足单调队列优化的条件中的“决策下标的下界单调”……不是,还不能这样说,因为这里的决策下标是j-x*w0,而不是x。
那么怎样才可以把决策下标变为x?

将决策下标按照模w0的余数进行分类,可以分成w0类,分别对应模w0余0、余1……余(w0-1)的情况。这时,上面方程中的所有决策下标j-x*w0都是同一类的。进一步,设q =j/w0(下取整),r=j%w0,则j=q*w0+r,对于某个决策下标j',设k=(j'-r)/w0,即j'=k*w0+r。显然可以发现,k的取值范围是:k>=0且q-s0<=k<=q,也即k的下界是max{0, q-s0}——随j的单调而单调。
然后,转移方程可以改为(这里把r当成一个已知量了):
F[q*w0+r] = max{F[k*w0+r]+(q-k)*v0} (k>=0且q-s0<=k<=q)
即F[q*w0+r]=max{F[k*w0+r]-k*v0}+q*v0 (k>=0且q-s0<=k<=q)
设G[k]=F[k*w0+r]得:
G[q]=max{G[k]-k*v0}+q*v0 (k>=0且q-s0<=k<=q)
这个方程已经可以使用单调队列来优化了!

这样可以得出算法:
(1)从1到n,枚举i,建立w[i]个空的单调队列,每个队列的元素都是两个int值:(k, val),表示转换后下标和决策值(G[k]-k*v[i]);
(2)从0到m,枚举j,得出q、r的值,对于队列r:
【1】删去队首过时(k<q-m[i])的元素;
【2】F[j]入队(这里的F[j]指上一阶段的F[j],即F[i-1][j]。因此这一步操作一定要先进行),删去队尾所有决策值val不大于(F[j]-q*v[i])的元素。
【3】取出队首结点,其val值加上q*v[i]后即为本阶段F[j]的值。
最后F[m]即为结果。总时间复杂度为O(NM)。

其实这个是可以推广的,即对于如下形式的转移方程(其中H、G和W均为常量,B[i]为决策下标的下界,随i单调):
F[i] = opt{F[i-x*H+W]}+G (B[i]<=i-x*H+W<i,x∈N
都可以用上述的办法进行转化,从而进行单调队列优化。

posted @ 2011-07-05 18:00 Mato_No1 阅读(5965) | 评论 (3)编辑 收藏

My first SRM……纪念一下

这次总体感觉还不是太差,也算正常发挥了——虽然最后还是米有搞定1000。250和500两道水题的速度应该还可以(从最终名次来看)。
另外,DIV2全场只有2位神犇搞定了1000……

Orz AHdoc等神犇
————————————————————————————————————
以下为1000题解(看别的神犇的代码搞懂的):
设F[i][j]为第i轮开始时(还未出牌时),面对的状态(内存)为j,是否必胜。这里设一开始的那一轮为第0轮。
逆推i。根据or运算的性质可以得到,若目前内存为j,某张已经出过的牌的值为K,则K的二进制的所有1位在j中对应的位也都是1(也就是j | K = j),这样,扫描每张牌,若其值K | j的值不等于j,则该牌不可能出过。因此,可以在第i轮出这张牌,若至少有一个F[i + 1][K | j]为必败状态则F[i][j]为必胜状态。
对于K | j的值等于j的牌,统计它们的张数,设为cnt。易知,前i轮出过的i张牌必然都是这种牌,因此若cnt>i,且F[i + 1][j]是必败状态,则可以在第i轮出一张这样的牌,必胜。
如果上面没有发现一个可以使F[i][j]为必胜状态的,则F[i][j]为必败状态。
边界:F[i][511]为必胜状态(0<=i<=N),F[N][j]为必败状态(0<=j<511,因为第N轮时已经木有牌了)。
最后,若F[0][0](初始状态)为必胜状态则先手必胜,否则先手必败。

posted @ 2011-07-03 02:09 Mato_No1 阅读(499) | 评论 (0)编辑 收藏

矩形的面积并问题:平面上有N个矩形,各边均平行于坐标轴,求它们覆盖的总面积(重复覆盖的只计一次)。
矩形的周长并问题:平面上有N个矩形,各边均平行于坐标轴,求它们覆盖形成的多边形的周长。

【算法】
面积并:
先将所有矩形的上边界和下边界作为水平线段记录下来,并对所有矩形的左右边界对应的横坐标离散化,设离散化后有N个横坐标,则中间有(N-1)段。对这(N-1)段建立线段树(注意,仍然和普通线段树一样,是双闭区间,不是网上说的一开一闭),然后,按照纵坐标递增顺序扫描前面记录的水平线段(设有M段),对每一段,如果是上边界,找到其离散化后的范围(只需找到其左右端点离散化后的值l、r,则对应范围为[l, r-1]),并插入线段[l, r-1],否则(下边界),删除线段[l, r-1]。再然后,线段树中的每个结点需要记录该区间内的线段覆盖的总长度len(若该区间被某条尚未删除的线段整体覆盖,则len=总长,否则len=左右子结点len之和),每次操作后,累加面积:T[root].len*该水平线段与下一条水平线段的纵坐标之差。

周长并:
类似,只不过由于组成周长的线段有水平的也有竖直的,线段树结点要记录的除了len意外还有一个ss,表示被线段覆盖的端点数量。另外还有lr和rr两个bool值,分别表示该线段的左端点和右端点是否被某条插入的线段覆盖。则T[x].ss = lch(T[x]).ss + rch(T[x]).ss - 2 * (lch(T[x]).rr && rch(T[x].lr)),若该线段被整体覆盖则T[x].ss=2(两端点)。最后,这次得到的T[root].len与上次得到的T[root].len之差的绝对值就是水平线段的长度,T[root].ss*纵坐标之差就是竖直线段的长度。

posted @ 2011-07-02 11:17 Mato_No1 阅读(2742) | 评论 (0)编辑 收藏

My first CF……惨挂,痛定思痛……
最终就过了2题(BD),
C题竟然把题意搞疵了,这里deselect是抵消的意思……也就是选的矩形是不能相交的……米搞懂……
A题,最后测试的时候挂了……原来是结果用int存储,忘了可能有前导0……
E题更是连看都米来得及看……
…………………………………………
DIV1是神犇组,DIV2是沙茶组,我在沙茶组里都被虐成这个样子,因此我已经沦为“沙茶中的沙茶”了……

Orz CLJ神犇、AHdoc神犇、xiaodao神犇……

posted @ 2011-07-01 09:51 Mato_No1 阅读(601) | 评论 (0)编辑 收藏

相关链接

Orz AHdoc!!!!!!!!!!!!!

这种神犇算法的关键在于真正利用了MST是一棵“树”的性质。也就是,它在求出MST后把它转化为有根树,然后,按长度递增顺序对于图中每一条不在MST中的边(i, j),找到树中i、j的最近公共祖先(LCA),记为p=LCA(i, j)。这样,树中i->p->j就是从i到j的路径。然后,依次扫描这条路径上的所有的边,将新边(i, j)的长度与路径上所有边的长度比较,找到长度差最小的(不过由于边(i, j)的长度一定不小于路径上所有边的长度,所以只要找到路径上最长边,则“删去这条最长边,加入边(i, j)”一定是所有加入的边为(i, j)的可行变换中代价最小的),取这个长度差最小的即可。不过最为神犇的一点是,这个算法在遍历完这条路径后,会将路径上所有的点(p点除外)的父结点全部设为p,也就是相当于并查集的路径压缩!这虽然会改变树的形态,但任何两点的LCA都是不会变的,因此不会影响后面的边。

注意上面“按长度递增顺序”是重点,原因是“路径压缩”可能会改变某些点之间的路径,也就是将某些点之间的路径长度减小。但是,很容易发现,被“压缩”的这些边必然是已经访问过的,也就是说这些边必然已经作为了前面的某条边(i, j),i到j路径上的边。对于这条边来说,可行变换中,新加入的边的长度应尽量小,因此,如果按长度递增顺序,则这些边在(i, j)之后肯定不会出现代价更小的可行变换,因此就可以将它们压缩,不会影响最优解。

复杂度分析:先不管求LCA的时间复杂度。设树中结点i的深度为h[i](h[root]=0)。对于树中的任意一个叶结点v,从root到v的路径的总长度(总边数)为h[v],因此,若某次要尝试的边(i, j)的某一端点(设为i)在从root到v的这条路径上,则p=LCA(i, j)一定也在这条路径上。这样,访问从i到p的路径上的总访问次数(也就是从i到p路径上的边数)为(h[i]-h[p])。在访问完成后,需要将从i到p路径上除p外的所有结点的父结点都设为p,也就是从root到v的路径的总长度减少了(h[i]-h[p]-1)。因此,在尝试所有不在MST中的边的过程中,访问从root到v的最初路径上的边的总次数不会超过(h[v]+这些边的总数)(这里h[v]指初始的h[v])。因此可以得到:访问树中所有边的总次数不会超过(最初所有叶结点深度之和+2*M),M为所有不在MST中边的总数!由于“最初所有叶结点深度之和”不会超过Nlog2N,因此总时间复杂度为O(Mlog2M+M+Nlog2N),其中O(Mlog2M)为用Kruskal求MST的时间,如果忽略这部分时间,则总时间复杂度为O(M+Nlog2N)。
其实这个算法的时间复杂度在忽略排序的情况下是线性的,即O(M+N),但本沙茶搞不懂怎么证明这一步。

下面是具体实现时的注意事项:
(1)将MST转化为有根树时,应用BFS,而不要用DFS,否则对于特殊数据可能爆栈;
(2)求LCA时,应用先让深度大的结点向上的方法(AHdoc神犇的方法),具体见下面的代码片段1;或者应用两者同时往上的方法(本沙茶的方法),具体见下面的代码片段2;否则,对于树是一条链,且每次访问都是访问最深的两个结点时,一次求LCA的时间复杂度可能升到O(N)。

【代码片段1】
int lca(int a, int b)
{
    
for (;;)
    {
        
if (a == b) return b;
        
if (h[a] >= h[b]) a = Fa[a]; else b = Fa[b];
    }
}
【代码片段2】
int LCA(int x, int y)
{
    
while (x && y) {
        
if (fl[x] == _s) return x; else fl[x] = _s;
        
if (fl[y] == _s) return y; else fl[y] = _s;
        x 
= pr[x]; y = pr[y];
    }
    
if (x) while (1if (fl[x] == _s) return x; else x = pr[x]; else while (1if (fl[y] == _s) return y; else y = pr[y];
}

【具体题目】Beijing2010 Tree(BZOJ1977)
这题要求严格次小生成树,因此在枚举的时候要注意,不能使可行变换的代价为0。
#include <iostream>
#include 
<stdio.h>
#include 
<algorithm>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
const int MAXN = 100001, MAXM = 300001;
const long long INF = ~0Ull >> 2;
struct edge {
    
int a, b, len;
    friend 
bool operator< (edge e0, edge e1) {return e0.len < e1.len;}
} E[MAXM];
struct edge0 {
    
int a, b, id, pre, next;
} E0[MAXM 
+ MAXM];
int n, m, m0, u[MAXN], pr[MAXN], No[MAXN], s[MAXM], Q[MAXN], fl[MAXN], _s;
long long mst_v = 0, res;
bool inmst[MAXM], vst[MAXN];
void init()
{
    scanf(
"%d%d"&n, &m);
    re(i, m) scanf(
"%d%d%d"&E[i].a, &E[i].b, &E[i].len);
}
int find(int x) {int r = x, r0; while (u[r] > 0) r = u[r]; while (u[x] > 0) {r0 = u[x]; u[x] = r; x = r0;} return r;}
void uni(int s1, int s2) {int tmp = u[s1] + u[s2]; if (u[s1] > u[s2]) {u[s1] = s2; u[s2] = tmp;} else {u[s2] = s1; u[s1] = tmp;}}
void init_d()
{
    re1(i, n) {E0[i].a 
= i; E0[i].pre = E0[i].next = i;}
    
if (n % 2) m0 = n + 1else m0 = n + 2;
}
void add_edge(int a, int b, int id)
{
    E0[m0].a 
= a; E0[m0].b = b; E0[m0].id = id; E0[m0].pre = E0[a].pre; E0[m0].next = a; E0[a].pre = m0; E0[E0[m0].pre].next = m0++;
    E0[m0].a 
= b; E0[m0].b = a; E0[m0].id = id; E0[m0].pre = E0[b].pre; E0[m0].next = b; E0[b].pre = m0; E0[E0[m0].pre].next = m0++;
}
void prepare()
{
    sort(E, E 
+ m);
    re1(i, n) u[i] 
= -1; init_d();
    
int s1, s2, z = 0;
    re(i, m) {
        s1 
= find(E[i].a); s2 = find(E[i].b);
        
if (s1 != s2) {z++; mst_v += E[i].len; add_edge(E[i].a, E[i].b, i); inmst[i] = 1; uni(s1, s2); if (z == n - 1break;}
    }
}
void bfs()
{
    re1(i, n) vst[i] 
= 0;
    Q[
0= 1; vst[1= 1;
    
int i, j;
    
for (int front=0, rear=0; front<=rear; front++) {
        i 
= Q[front];
        
for (int p=E0[i].next; p != i; p=E0[p].next) {
            j 
= E0[p].b;
            
if (!vst[j]) {
                vst[j] 
= 1; Q[++rear] = j; pr[j] = i; No[j] = E0[p].id;
            }
        }
    }
}
int LCA(int x, int y)
{
    
while (x && y) {
        
if (fl[x] == _s) return x; else fl[x] = _s;
        
if (fl[y] == _s) return y; else fl[y] = _s;
        x 
= pr[x]; y = pr[y];
    }
    
if (x) while (1if (fl[x] == _s) return x; else x = pr[x]; else while (1if (fl[y] == _s) return y; else y = pr[y];
}
void sol0(int a, int b, int l)
{
    
int p = LCA(a, b), p0, No0;
    
while (a != p) {No0 = No[a]; if (!s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[a]; pr[a] = p; a = p0;}
    
while (b != p) {No0 = No[b]; if (!s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[b]; pr[b] = p; b = p0;}
}
void solve()
{
    pr[
1= 0; bfs();
    re(i, m) 
if (!inmst[i]) {_s = i + 1; sol0(E[i].a, E[i].b, E[i].len);}
    res 
= INF;
    re(i, m) 
if (inmst[i] && s[i] && s[i] < res) res = s[i];
    res 
+= mst_v;
}
void pri()
{
    cout 
<< res << endl;
}
int main()
{
    init();
    prepare();
    solve();
    pri();
    
return 0;
}

posted @ 2011-07-01 09:10 Mato_No1 阅读(2713) | 评论 (8)编辑 收藏

在没有修改操作时,应用划分树可以在O(MlogN)时间内解决查找区间第K小的问题,但是在引入修改(将原序列中的某个值改为另一个值)之后,划分树就不行了。
这时,需要数据结构联合的思想。
可以观察一下:
(1)区间操作:使用线段树;
(2)修改值(其实是先删除再插入)和找第K小:使用平衡树;
现在这两种操作都有,应该使用线段树+平衡树
准确来说是线段树套平衡树,即对原序列建立一棵线段树,其中的每个结点内套一棵对该结点管辖区间内的平衡树。

<1>结点类型(结构):
struct seg_node {
    
int l, r, mid, lch, rch, rt;
} T0[MAXN0];
struct SBT_node {
    
int v, l, r, p, sz0, sz, mul;
} T[MAXN];
其中seg_node是线段树结点类型,SBT_node是平衡树(SBT)结点类型。需要注意的是seg_node里面的rt域(root的缩写),它是该结点内套的平衡树的根结点下标索引(因为对于任意一棵平衡树,只要知道了其根结点就可以遍历整棵树)。

<2>建树:
建树是线段树和平衡树一起建。在建立线段树结点的时候,先建立一棵空的平衡树(rt域置0),然后再在平衡树里面逐个插入该结点管辖区间内的所有元素即可;

<3>修改:
修改操作要注意:如果要将A[x](A为原序列)的值修改为y,则需要自顶向下遍历整棵线段树,将所有包含了A[x]的结点内的平衡树全部执行“删除v=A[x](这个可以通过真正维护一个序列得到),再插入y”的操作;

<4>找区间第K小:
这个操作极其麻烦。需要借助二分。
设要在区间[l, r]中找到第K小。首先将[l, r]拆分成若干个线段树结点,然后二分一个值x,在这些结点的平衡树中找到x的rank(这里的rank指平衡树中有多少个值比x小,不需要加1),加起来,最后再加1,就是x在[l, r]中的总名次。问题是,设[l..r]中第K小的数为v1,第(K+1)小的数为v2(如果不存在的话,v2=+∞),则[v1, v2)内的数都是“第K小”的。因此,不能二分数字,而应该二分元素。设S[i]为原序列中第i小的数,二分i,然后在根结点的平衡树中找到第i小的即为S[i],再求其名次,这样直到找到总名次为K的元素为止。问题还没完,序列中可能有元素的值相同,这时可能永远也找不到第K小的(比如序列1 2 3 3 3 4 5,K=4,若“序列中比x小的元素总数+1”为x的名次,则永远也找不到第4小的),因此,若这样求出的“名次”小于等于K,都应该将下一次的左边界设为mid而不是(mid+1),而“名次”大于K时,该元素肯定不是第K小的,所以下一次右边界设为(mid-1)。

代码(本机测最猥琐数据4s以内,交到ZJU上TLE,不知为什么,神犇指点一下,3x):
#include <iostream>
#include 
<stdio.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
const int MAXN0 = 110000, MAXN = 930000, INF = ~0U >> 2;
struct seg_node {
    
int l, r, mid, lch, rch, rt;
} T0[MAXN0];
struct SBT_node {
    
int v, l, r, p, sz0, sz, mul;
} T[MAXN];
int No0, No, n, root, rt0, a[MAXN0 >> 1], b[MAXN0 >> 1], l1, r1, len;
void slc(int _p, int _c)
{
    T[_p].l 
= _c; T[_c].p = _p;
}
void src(int _p, int _c)
{
    T[_p].r 
= _c; T[_c].p = _p;
}
void upd(int x)
{
    T[x].sz0 
= T[T[x].l].sz0 + T[T[x].r].sz0 + T[x].mul;
    T[x].sz 
= T[T[x].l].sz + T[T[x].r].sz + 1;
}
void lrot(int x)
{
    
int y = T[x].p; if (y == rt0) T[rt0 = x].p = 0else {int p = T[y].p; if (y == T[p].l) slc(p, x); else src(p, x);}
    src(y, T[x].l); slc(x, y); T[x].sz0 
= T[y].sz0; T[x].sz = T[y].sz; upd(y);
}
void rrot(int x)
{
    
int y = T[x].p; if (y == rt0) T[rt0 = x].p = 0else {int p = T[y].p; if (y == T[p].l) slc(p, x); else src(p, x);}
    slc(y, T[x].r); src(x, y); T[x].sz0 
= T[y].sz0; T[x].sz = T[y].sz; upd(y);
}
void maintain(int x, bool ff)
{
    
int z;
    
if (ff) {
        
if (T[T[T[x].r].r].sz > T[T[x].l].sz) {z = T[x].r; lrot(z);}
        
else if (T[T[T[x].r].l].sz > T[T[x].l].sz) {z = T[T[x].r].l; rrot(z); lrot(z);} else return;
    } 
else {
        
if (T[T[T[x].l].l].sz > T[T[x].r].sz) {z = T[x].l; rrot(z);}
        
else if (T[T[T[x].l].r].sz > T[T[x].r].sz) {z = T[T[x].l].r; lrot(z); rrot(z);} else return;
    }
    maintain(T[z].l, 
0); maintain(T[z].r, 1); maintain(z, 0); maintain(z, 1);
}
int find(int _v)
{
    
int i = rt0, v0;
    
while (i) {
        v0 
= T[i].v;
        
if (_v == v0) return i; else if (_v < v0) i = T[i].l; else i = T[i].r;
    }
    
return 0;
}
void ins(int _v)
{
    
if (!rt0) {
        T[
++No].v = _v; T[No].l = T[No].r = T[No].p = 0; T[No].sz0 = T[No].sz = T[No].mul = 1; rt0 = No;
    } 
else {
        
int i = rt0, j, v0;
        
while (1) {
            T[i].sz0
++; v0 = T[i].v;
            
if (_v == v0) {T[i].mul++return;} else if (_v < v0) j = T[i].l; else j = T[i].r;
            
if (j) i = j; else break;
        }
        T[
++No].v = _v; T[No].l = T[No].r = 0; T[No].sz0 = T[No].sz = T[No].mul = 1if (_v < v0) slc(i, No); else src(i, No);
        
while (i) {T[i].sz++; maintain(i, _v > T[i].v); i = T[i].p;}
    }
}
void del(int x)
{
    
if (T[x].mul > 1) {
        T[x].mul
--;
        
while (x) {T[x].sz0--; x = T[x].p;}
    } 
else {
        
int l = T[x].l, r = T[x].r;
        
if (!|| !r) {
            
if (x == rt0) T[rt0 = l + r].p = 0else {
                
int p = T[x].p; if (x == T[p].l) slc(p, l + r); else src(p, l + r);
                
while (p) {T[p].sz0--; T[p].sz--; p = T[p].p;}
            }
        } 
else {
            
int i = l, j;
            
while (j = T[i].r) i = j;
            T[x].v 
= T[i].v; T[x].mul = T[i].mul; int p = T[i].p; if (i == T[p].l) slc(p, T[i].l); else src(p, T[i].l);
            
while (p) {upd(p); p = T[p].p;}
        }
    }
}
int Find_Kth(int K)
{
    
int i = rt0, s0, m0;
    
while (i) {
        s0 
= T[T[i].l].sz0; m0 = T[i].mul;
        
if (K <= s0) i = T[i].l; else if (K <= s0 + m0) return T[i].v; else {K -= s0 + m0; i = T[i].r;}
    }
}
int rank(int _v)
{
    
int i = rt0, tot = 0, v0;
    
while (i) {
        v0 
= T[i].v;
        
if (_v == v0) {tot += T[T[i].l].sz0; return tot;} else if (_v < v0) i = T[i].l; else {tot += T[T[i].l].sz0 + T[i].mul; i = T[i].r;}
    }
    
return tot;
}
int mkt(int l, int r)
{
    T0[
++No0].l = l; T0[No0].r = r; int mid = l + r >> 1; T0[No0].mid = mid; rt0 = 0;
    re3(i, l, r) ins(a[i]); T0[No0].rt 
= rt0;
    
if (l < r) {int No00 = No0; T0[No00].lch = mkt(l, mid); T0[No00].rch = mkt(mid + 1, r); return No00;} else {T0[No0].lch = T0[No0].rch = 0return No0;}
}
void fs(int x)
{
    
if (x) {
        
int l0 = T0[x].l, r0 = T0[x].r;
        
if (l0 >= l1 && r0 <= r1) b[len++= T0[x].rt; else if (l0 > r1 || r0 < l1) returnelse {fs(T0[x].lch); fs(T0[x].rch);}
    }
}
void C(int x, int _v)
{
    
int i = root, l0, r0, mid0, v0 = a[x], N;
    
while (i) {
        l0 
= T0[i].l; r0 = T0[i].r; mid0 = T0[i].mid; rt0 = T0[i].rt;
        N 
= find(v0); del(N); ins(_v); T0[i].rt = rt0;
        
if (x <= mid0) i = T0[i].lch; else i = T0[i].rch;
    }
    a[x] 
= _v;
}
int Q(int K)
{
    len 
= 0; fs(root);
    
int ls = 1, rs = n, mids, midv, tot;
    
while (ls < rs) {
        mids 
= ls + rs + 1 >> 1; rt0 = T0[root].rt; midv = Find_Kth(mids);
        tot 
= 1; re(i, len) {rt0 = b[i]; tot += rank(midv);}
        
if (tot <= K) ls = mids; else rs = mids - 1;
    }
    rt0 
= T0[root].rt; return Find_Kth(ls);
}
int main()
{
    
int tests, m, x, y, K;
    
char ch;
    scanf(
"%d"&tests);
    re(testno, tests) {
        scanf(
"%d%d"&n, &m); No0 = No = 0;
        re(i, n) scanf(
"%d"&a[i]); ch = getchar();
        root 
= mkt(0, n - 1);
        re(i, m) {
            ch 
= getchar();
            
if (ch == 'C') {
                scanf(
"%d%d%*c"&x, &y);
                C(
--x, y);
            } 
else {
                scanf(
"%d%d%d%*c"&l1, &r1, &K);
                l1
--; r1--; printf("%d\n", Q(K));
            }
        }
    }
    
return 0;
}

posted @ 2011-06-27 21:53 Mato_No1 阅读(2468) | 评论 (0)编辑 收藏

仅列出标题
共12页: First 4 5 6 7 8 9 10 11 12