摘要: 【背景(神犇不要鄙视)】本沙茶在搜索题目的调试上已经挂过两次了(NOI2011的game用了2h+木有调出来,还耽误了继续想show的时间,结果被挡在了集训队门外;NOIP2011的mayan,用暴搜都调了2h+,木有调出来,结果惨挂,全国第200名,如果这题AC了就是全国前50名……),为了防止在接下来的比赛中再在这里挂掉,本沙茶决定好好搞一下这个。【DFS的调试技巧】如...  阅读全文

posted @ 2012-03-22 20:49 Mato_No1 阅读(701) | 评论 (1)编辑 收藏

转眼间这个空间已经用了一年了……
今天又看了一下在这里写的N多总结,真的觉得自己在最近一年之内提高的不少啊囧……虽然这很多都只是模板……离NOI、CTSC的要求还差很远……
不过现在的时间还来得及……只要好好努力的话是还有机会达到神犇水平的……(虽然本沙茶已经比WJMZBMR落后一年了囧)
向着NOI、CTSC、IOI冲刺啊啊……

另外:可以看出,这里的大部分总结都是关于数据结构的……确实,本沙茶在最近一年之内主要精力都花在数据结构上……也掌握了不少东东(树状数组应用、线段树应用、S(eg)playtree、SBT、树的路径剖分、块状数组、动态树、以及一些用于字符串的数据结构)……但是,本沙茶还有很多弱项,有的甚至几乎一无所知……后面要调整到这上面来了囧……

posted @ 2012-03-19 23:13 Mato_No1 阅读(410) | 评论 (0)编辑 收藏

刚捉完……这次的题目感觉和前几次难度差不多啊囧……还木有被虐得太惨(当然,我是沙茶,被虐是必然的)

krizaljka: 超级大水题;
eko: 如果真是用裸的二分法(不T)的话,就是超级大水题;
dna: 水题,从后往前扫描,如果遇到B,就进行一次变换(如果该B位的前一位也是B,则进行整体取反,否则,即该B位的前一位是A或者该B位在最前面,则进行单位取反),可以用一个bool记录前面目前是否被取反了;
razbibriga: 水题,直接枚举四个角的字母就行了,然后在计数的时候,要排除掉同一个字符串被用多次的情况,因此对于2行2列的4个字符串中有首尾字母都相同的要特判一下,具体的特殊情况有点多,这里不列举了囧;
blokovi: 神犇题!本沙茶只会暴力;
poplocavanje: 神犇题!本沙茶只会暴力;

结果……前4道水题AC了,blokovi竟然搞对了7个点(这……难道贪心是正解?),但是poplocavanje得分比预想的要低了囧(不知是哪里疵了)……总分478,rank17(全国除了ZL外的神犇都木有参加,说明我在沙茶中都是rank16……哭死……)

posted @ 2012-03-18 01:14 Mato_No1 阅读(968) | 评论 (3)编辑 收藏

【背景(神犇不要鄙视)】
前段时间,本沙茶在捉神马题都被完虐的情况下,发现了COCI……一看,发现里面有相当数量的水题,于是就去捉了……结果,本想体验虐题的感觉,可还是被里面的一些神犇题虐了……我太沙茶了,没脸见人了囧……

COCI官网

2011~2012 #1:
jabuke: 超级大水题;
matrix:超级大水题,不过本沙茶一开始看疵题了……
x3: 水题,直接对每一位单独考虑即可;
ples: 水题,裸DP;
sort: 这个题看上去很不好搞囧……但注意题目里面的这个条件:一开始各极大递减子序列的长度均为偶数(也就是均>1),这样,第一次模拟一遍以后,剩下的极大递减子序列就只有长度为2的了,这时每个数要归位需要与其后面所有比它小的数都交换一次,所以结果就是第一次模拟的rev执行次数加上第一次模拟之后的逆序对总数;
skakac: 神犇题,因为涉及比较难的知识点,本沙茶暂时不会搞囧……

2011~2012 #2:
najboljih5: 超级大水题;
okret: 超级大水题,注意特殊情况即可;
zadaca: 水题,直接因数分解一遍,再查找相同的因数(用哈希),求较小值即可,对于10^9的判定应该很容易的,注意特殊情况;
kompici: 中等难度,需要用到容斥原理,对于开始的10^6个数,由于本质不同的只有1024个,所以可以压缩成1024种情况,这样总的复杂度就是1024*1024了;
funkcija: 神犇题!!巨神无比的递推!!这里面涉及到的思想需要慢慢总结;
raspored: 中等难度,模型转化后可以发现T是无用的,只需要按照时间递增的顺序执行任务(贪心的经典模型),然后用线段树维护这个递增序的和就行了;

2011~2012 #3:
digitalna: 超级大水题;
dhondt: 超级大水题,关键在于题意的理解(是把每个派别的选票总数依次除以1到14,得14个结果,然后汇总起来取前14大的结果对应的派别,不是按比例);
pogodak: 水题,暴力模拟即可;
robot: 水题,注意二分查找的边界(比如要找大于等于给定值的最小值,需要特判所有的值都小于给定值的情况);
place: 超级大水题,裸得不能再裸的模型了;
traka: 本张试卷的唯一一道不水的题(是个神犇题),首先很容易模型转化为求F[i]S[i-1]-F[i+1]S[i]的最大值,由于F是个定值且为正,可以除以F[i],变成S[i-1]-(F[i+1]/F[i])*S[i],可以看成直线y=S[i-1]-S[i]*x,当x=F[i+1]/F[i]时的纵坐标,这样把所有的直线搞出来,维护下凸壳即可(当然本沙茶至今未做过这样数形结合的题目囧……以后可以搞一个专题);

2011~2012 #4:
kino: 超级大水题,贪心就能搞定;
zima: 水题,线段树操作,注意细节(本沙茶一开始把下放标记dm()中的mr_opr(LCH(No)),mr_opr写成dm了……成递归调用了……为此查了2h+);
keks:超级大水题,贪心经典模型,不要管前导0的问题;
ograda:这个是神犇题了(因为本沙茶总是搞不定啊囧……),首先由于相邻元素的大小关系以定,绝对值号可以去掉的(本沙茶竟然木有想到这个),然后根据贪心思想,应当尽量把大的和小的交替放置,而且这样必然能得到可行解(详细证明见官方题解);
broj:中等难度,P>=5000时可以直接筛,P<5000时用容斥原理(表面上需要计算2N次,N是小于P的质数总数,其实很多交集都是空集,可以忽略掉,最后剩下的非空集合很少的囧……这也是容斥原理之所以广泛应用的原因啊囧……)
kriptogram: 中等难度,首先各个单词可以映射到Trie里面,变成编号,然后就是类似KMP的搞法了(类似于WC2012 Day1上午讲的那道CEOI题目)……本沙茶用官方数据本机测试AC,但交上去RE了两个点……说是Trie爆了……(本机测试时跟踪了一下,发现木有爆)主要是这题空间卡得太死(64M),而Trie的空间由于要乘上一个104,所以不能开太大(或许这里可以优化,但本沙茶还不会啊囧……)

posted @ 2012-03-16 20:56 Mato_No1 阅读(1999) | 评论 (0)编辑 收藏

COCI 2011~2012 #2 funkcija
其思想的巧妙程度以及各种细节的处理难度远超AHOI2009的cchess(以前本沙茶总以为这种才是最神的递推题囧……)

具体的题解以及方法归纳以后再写,先上代码:
#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
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++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
#define ll long long
const int MAXN = 27, MAXM = 100010, MOD = 1000000007, INF = ~0U >> 2;
struct edge {
    
int a, b, pre, next;
} E[(MAXN 
<< 2+ 1];
int n, m, A[MAXN][2];
ll F[MAXN][MAXM], G[MAXN][MAXM], res;
void init_d()
{
    re1(i, n) E[i].pre 
= E[i].next = i; m = n + 1;
}
void add_edge(int a, int b)
{
    E[m].a 
= a; E[m].b = b; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m++;
}
void init()
{
     scanf(
"%d"&n);
     
char ss0[20], ss1[20];
     re1(i, n) {
         scanf(
"%s %s", ss0, ss1);
         
if (ss0[0>= 48 && ss0[0<= 57) A[i][0= atoi(ss0); else A[i][0= 96 - ss0[0];
         
if (ss1[0>= 48 && ss1[0<= 57) A[i][1= atoi(ss1); else A[i][1= 96 - ss1[0];
     }
     init_d();
     re1(i, n) {
         
if (A[i][0< 0) add_edge(-A[i][0], i);
         
if (A[i][1< 0) add_edge(-A[i][1], i);
     }
}
void solve()
{
    
int x, y, tmp;
    rre1(i, n) re2(j, 
1, MAXM) {
        F[i][j] 
= 1;
        
for (int p=E[i].next; p != i; p=E[p].next) {
            x 
= E[p].b;
            
if (A[x][0< 0) {
                
if (A[x][1>= j) {
                    tmp 
= G[x][A[x][1]] - G[x][j - 1]; if (tmp < 0) tmp += MOD;
                    F[i][j] 
= F[i][j] * tmp % MOD; 
                } 
else {F[i][j] = 0break;}
            } 
else {
                
if (A[x][0<= j) {
                    tmp 
= G[x][j] - G[x][A[x][0- 1]; if (tmp < 0) tmp += MOD;
                    F[i][j] 
= F[i][j] * tmp % MOD;
                } 
else {F[i][j] = 0break;}
            }
        }
        G[i][j] 
= (G[i][j - 1+ F[i][j]) % MOD;
    }
    res 
= 1;
    re1(i, n) 
if (A[i][0> 0 && A[i][1> 0) {
        tmp 
= G[i][A[i][1]] - G[i][A[i][0- 1]; if (tmp < 0) tmp += MOD;
        res 
= res * tmp % MOD;
    }
}
void pri()
{
     cout 
<< res << endl;
}
int main()
{
    init();
    solve();
    pri();
    
return 0;
}

posted @ 2012-03-12 23:30 Mato_No1 阅读(497) | 评论 (0)编辑 收藏

原题地址
说实话我第一次尝试写炮兵阵地是在2009年……已经过去两年多了,终于找到了一个好的解法……庆祝一下……

【状态压缩DP】
所谓状态压缩DP,就是对于某些DP问题,每一阶段的状态都有很多维,要利用某些手段将它们压缩到一维(一个正整数),顺便进行状态的精简(去掉不合法的状态),然后再进行DP。这里讲的主要是传统的状压DP,有一种基于“插头”的DP,更高级,以后再搞。
对于本题,可以设计出一个这样的状态:[0..1][0..1][0..1]...[0..1](有M个[0..1]),表示该行的每个格子放不放炮兵,如果放,为1,否则为0。显然,这是一个M位二进制数,如果能把它们压缩成一个int就好了。

【如何压缩】
第一个问题是这么多维的状态如何压缩的问题。
对于本题,由于是二进制数,直接压缩就可以了。但是对于某些情况,状态是个三进制数(每个格子的属性都有3种)甚至更多进制数,这样,直接压缩会导致无法使用位运算,从而使“解压”变得很麻烦,耗时也很长(需要暴力),因此,可以将每位三进制都拆成两位二进制:
0->00
1->01
2->10
(当然1拆成10、2拆成11也木有问题,只要能区分开就行了)
这样,每个状态就可以用2个二进制数来表示,可以在构造出所有合法状态以后将每个状态所对应的两位二进制数存到struct里面,使用时调出即可。

【如何精简】
这个问题是最重要的,因为,如果不精简,在枚举状态以及转移的时候就会枚举到很多不合法状态,导致时间浪费。
所谓精简,是指在预处理以及DP过程中,尽量避开不合法状态。
(1)预处理中的精简:
包括3个部分:
<1>找到所有可能的合法状态并编号:根据题意限制,有的状态在阶段内就不合法(比如本题,一行一阶段,那么凡是有两个1位的距离小于2的状态都不合法),而且这种状态所占的比重往往还很大(本题中,M=10时,也只有60种可能的合法状态),此时,为了找到这些合法状态,可以DFS构造实现。
需要注意的是,有的题不光要找到一个阶段内的合法状态,还要找到两个或两个以上阶段内的合法状态(比如那个有关多米诺骨牌的题),此时需要两个int同时DFS;
在找到合法状态以后,需要对每个合法状态进行编号,以达到“压缩”的目的。这里就涉及到了状态编号和状态表示的问题:比如,状态1001,表示为9,在DFS中第一个被搜到,因此编号为0,不要搞混了这两个(尤其不要搞混“编号为0”和“状态表示为0”,它们是不同的)。在预处理和DP的过程中,所有涉及到状态的数组下标,全部是编号而不是表示,知道编号要求表示,可以在DFS中记录的数组里面调,而知道表示要求编号,可以利用逆数组或者哈希;
<2>找到每一阶段的合法状态:即使是<1>中被判定为合法的状态,在具体的各个阶段中也未必合法(比如本题,如果某一行的某一个位置是'H',不能放,而某一个状态在这里放了,则不合法),因此要对每个阶段再枚举一遍,找到真正合法的状态,并计入一个vector;
<3>找到状态转移中的合法状态:在状态转移中,往往要求状态不冲突(比如本题,在连续的三个阶段中,都不能有某一位有两个为1的情况),因此,还要枚举每个状态在转移时与其不冲突的状态,并计入vector。
注意,有时候这一步不是很容易进行,需要在DP过程中进行;
(2)DP过程中的精简:
DP过程中,枚举状态、转移决策都只枚举合法的,在vector里面调(注意vector里记录的全都是状态编号而不是表示!),可以大大减少枚举量,不过有时候,还会有一些时间浪费,这时候,可以采取一些其它的办法来精简,比如再次进行DFS构造合法状态等。

总之,这类问题的目标就是“精简,精简,再精简,使枚举到的不合法状态减到最少”。
代码:
#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
#include 
<vector>
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++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
#define pb push_back
#define IR iterator
typedef vector 
<int> vi;
const int MAXN = 103, MAXM = 11, MAXS = 100, INF = ~0U >> 2;
int n, m, S, A[MAXN], B[MAXS], T1[MAXS], F[MAXN][MAXS][MAXS], res;
bool F0[MAXN][MAXS];
vi P0[MAXN], P1[MAXS][MAXS];
void init()
{
     scanf(
"%d%d"&n, &m); getchar();
     re1(i, n) {A[i] 
= 0; re(j, m) A[i] |= ((getchar() == 'P'<< j); getchar();}
}
void dfs(int v, int ST)
{
    
if (v >= m) B[S++= ST; else {dfs(v + 3, ST | (1 << v)); dfs(v + 1, ST);}
}
void prepare()
{
    S 
= 0; dfs(00);
    re(i, S) {T1[i] 
= 0for (int j=B[i]; j; j-=j&(-j)) T1[i]++;}
    re1(i, n) re(j, S) 
if (!(~A[i] & B[j])) {P0[i].pb(j); F0[i][j] = 1;} P0[0].pb(S - 1); F0[0][S - 1= 1;
    re(i, S) re(j, S) 
if (!(B[i] & B[j])) re(k, S) if (!(B[i] & B[k]) && !(B[j] & B[k])) P1[i][j].pb(k);
}
void solve()
{
    re3(i, 
0, n) re(j1, S) re(j2, S) F[i][j1][j2] = -INF; F[0][S - 1][S - 1= 0;
    vi::IR vi_e0, vi_e1, vi_e2; 
int j0, j1, k, V;
    re(i, n) {
        vi_e0 
= P0[i].end(); if (i) vi_e1 = P0[i - 1].end(); else vi_e1 = P0[i].end();
        
for (vi::IR p=P0[i].begin(); p != vi_e0; p++)
            
for (vi::IR p_=P0[i ? i - 1 : i].begin(); p_ != vi_e1; p_++) {
                j0 
= *p; j1 = *p_;
                
if (!(B[j0] & B[j1])) {
                    vi_e2 
= P1[j0][j1].end();
                    
for (vi::IR p__ = P1[j0][j1].begin(); p__ != vi_e2; p__++) {
                        k 
= *p__;
                        
if (F0[i + 1][k]) {
                            V 
= F[i][j0][j1] + T1[k];
                            
if (V > F[i + 1][k][j0]) F[i + 1][k][j0] = V;
                        }
                    }
                }
            }
    }
    res 
= 0; re(i, S) re(j, S) if (F[n][i][j] > res) res = F[n][i][j];
}
void pri()
{
     printf(
"%d\n", res);
}
int main()
{
    init();
    prepare();
    solve();
    pri();
    
return 0;
}

posted @ 2012-03-10 23:27 Mato_No1 阅读(827) | 评论 (0)编辑 收藏

【背景】
2012年1月19日,本沙茶开始看动态树论文,搞懂了一些;
2012年1月20日,开始写动态树,使用的题是QTREE,花了整整一天时间总算写完,交上去,TLE……
2012年1月21日,又调了一天,对拍了100+组随机数据都瞬间出解,交上去,还是TLE……
2012年2月8日,WC2012,fanhq666讲动态树,又搞懂了一点,于是当天晚上回房间以后就开始继续调,交上去,TLE……
2012年2月9日,晚上继续调QTREE,TLE……
在挑战动态树N次失败之后,本沙茶昨天再次去挑战动态树……这次换了一个题:BZOJ2002(传说中的动态树模板题)
一开始还是TLE了,不过后来把数据搞到手以后,发现TLE的原因并不是常数大,而是死循环了,最后,经过2h+的调试,总算找到了错误(有好几处),终于AC了……

【关于BZOJ2002】
从每个点i往(i+Ki)连一条边,如果(i+Ki)不存在则往一个附加的结点(本沙茶的代码中为1号点,因为0号点是不能使用的)连一条边,这样就是一棵树(除1号点外,每个点有且只有一个后继……),然后,问题中的两种操作就是“改接”和“询问到根的距离”,可以用动态树搞;

【代码】
#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
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++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
const int MAXN = 200004, INF = ~0U >> 2;
struct node {
    
int c[2], p, sz;
    
bool rf, d;
} T[MAXN];
int n;
void sc(int _p, int _c, bool _d)
{
    T[_p].c[_d] 
= _c; T[_c].p = _p; T[_c].d = _d;
}
void upd(int No)
{
    T[No].sz 
= T[T[No].c[0]].sz + T[T[No].c[1]].sz + 1;
}
void rot(int No)
{
    
int p = T[No].p; bool d = T[No].d;
    
if (T[p].rf) {T[p].rf = 0; T[No].rf = 1; T[No].p = T[p].p;} else sc(T[p].p, No, T[p].d);
    sc(p, T[No].c[
!d], d); sc(No, p, !d); upd(p);
}
void splay(int No)
{
    
int p; while (!T[No].rf) if (T[p = T[No].p].rf) rot(No); else if (T[No].d == T[p].d) {rot(p); rot(No);} else {rot(No); rot(No);} upd(No);
}
int access(int x)
{
    
int tmp = 0;
    
do {
        splay(x); T[T[x].c[
1]].rf = 1; T[tmp].rf = 0; sc(x, tmp, 1); upd(x); tmp = x; x = T[x].p;
    } 
while (x);
}
void cut(int x)
{
    access(x); splay(x); T[T[x].c[
0]].rf = 1; T[T[x].c[0]].p = 0; sc(x, 00); upd(x);
}
void join(int x, int p)
{
    access(x); T[x].p 
= p;
}
int main()
{
    
int m, x, y, z;
    scanf(
"%d"&n); n++;
    re3(i, 
2, n) {scanf("%d"&x); T[i].sz = 1; T[i].rf = 1if (i + x <= n) T[i].p = i + x; else T[i].p = 1;}
    T[
1].sz = 1; T[1].rf = 1; T[0].sz = 0;
    scanf(
"%d"&m);
    re(i, m) {
        scanf(
"%d"&x);
        
if (x == 1) {
            scanf(
"%d"&y); y += 2;
            access(y); splay(y); printf(
"%d\n", T[T[y].c[0]].sz);
        } 
else {
            scanf(
"%d%d"&y, &z); y += 2;
            cut(y); 
if (y + z <= n) join(y, y + z); else join(y, 1);
        }
    }
    
return 0;
}

【易疵点】
(1)注意一个点的父结点p有两种可能:如果该结点是某棵伸展树的根结点则p为它通过轻边连向的另一棵伸展树中的某一个点的编号(在原树中,就是该结点所在伸展树代表的链的最上层的那个节点的父结点),否则为该结点在伸展树中的父结点编号(通过重边相连);
(2)在改接时删除边的时候,如果删除的是轻边则直接把父结点设为0即可,如果是重边则要sc一下再将父结点设为0;
(3)rot里面有一个地方很关键,极易疵!就是如果p是伸展树的根结点,则除了No的rf改为1,p的rf改为0之外,还要把No的父结点设为p的父结点;
(4)本题中不涉及整棵大树的根(就是root),如果需要root,则rot中还要加一句:if (root == p) root = No;
(5)cut里面有一种简便写法(不需要找到x的父结点的):先access(x),将x伸展到根之后,x及其右子树就是原树中以x为根的子树,左子树就是其它部分,所以直接将x与其左子结点断开即可(注意断开的是重边所以要sc一下,再将x的左子结点的父结点设为0、rf设为1,再upd一下);
(6)一定要搞清楚rf的变化(该改时一定要改!)

最后,放上fanhq666超级大神的总结:
01

posted @ 2012-02-26 13:03 Mato_No1 阅读(2054) | 评论 (2)编辑 收藏

所谓数据结构询问类题,就是那种一大堆操作+询问的题(典型的有NOI2005 sequence、NOI2007 necklace等)。
对于这种题常用数据结构有:线段树(树状数组可以看成线段树的简化版本)、Segplaytree、各种块状结构,以及线段树在树结构上的应用——树链剖分、Segplaytree在树结构上的应用——Link-cut Tree实现动态树;

解题技巧:
(1)看到题以后,首先搞清楚题目是基于什么结构的——线性结构、树形结构、甚至可能还有图结构(比如SCOI2011的那题,对于图结构一般不能直接处理,而是采用三种办法搞定:一是有向图缩环转化为有向树、二是求生成树、三是遍历)
(2)在基本的数据结构(上面列出来的那些)当中看看有木有可以支持所有的操作的(其实,如果遇到一种基本数据结构就能支持题目中所有的操作,那这题就太水了,一般像AH这样的弱省全场30人以上AC木有问题,因此要特别小心),如果有就直接用这种数据结构搞了;
(3)如果木有,就要想到数据结构的联合或者是模型转化;
(4)然后就是写代码了,写的时候要注意这种数据结构模板中的易疵点;
(5)调试的时候,先检查样例和不超过5组的小数据,紧接着读一遍代码,观察那些易疵点,然后立刻开始对拍,节省时间;
(6)对拍的程序是很容易写的;
(7)造数据的时候可以造只有一种操作的,专门检查这种操作有木有问题;
(8)一定要设法考虑到并检查各种特殊情况。

posted @ 2012-01-22 23:08 Mato_No1 阅读(282) | 评论 (0)编辑 收藏

例题
本沙茶觉得块状数组又好写又有用(其实就是另一种朴素)……只是那个O(sqrt(n))的复杂度比较大而已(其实如果加上常数的话它并不比Segplaytree慢多少)
编程技巧:
(1)每块长度设为m=floor(sqrt(n)),最后不足长度的不补值,设n0为总块数(显然n0=(n-1)/m+1);
(2)设立LEN[i]=第i块的实际长度(显然除了最后一块都是m),可以在建立块状数组(真正搞成块状,也就是二维)的时候得到;
(3)对于区间[l, r],要注意:<1>l、r位于同一块(l/m==r/m)的情况;<2>r位于最后一块的情况;
(4)别忘了同时更新原数组与块状数组;

另外,本例题需要二分+找多少个比它小的这样的操作,总时间复杂度是O(N*sqrt(N)*log2N*log2N)(幸亏N只有10000……)。

如果有插入删除元素,就需要用动态的块状链表了……极其难搞,本沙茶不敢试了……遇到这种题还是写Segplaytree吧囧……

#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
#include 
<math.h>
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++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
const int MAXN = 100002, MAXM = 320, INF = ~0U >> 2;
int n, m, n0, A[MAXN], T[MAXM][MAXM], LEN[MAXM], res;
int cmp(const void *s1, const void *s2)
{
    
return *(int *)s1 - *(int *)s2;
}
void prepare()
{
    m 
= (int) floor(sqrt(n) + 1e-7); n0 = (n - 1/ m + 1; re(i, n0) LEN[i] = 0;
    re(i, n) T[i 
/ m][LEN[i / m]++= A[i];
    re(i, n0) qsort(T[i], LEN[i], 
sizeof(int), cmp);
}
void opr0(int No, int x)
{
    A[No] 
= x; int S = No / m; re(i, LEN[S]) T[S][i] = A[S * m + i]; qsort(T[S], LEN[S], sizeof(int), cmp);
}
int opr1(int l, int r, int x)
{
    
int S0 = l / m, l0 = l % m, S1 = r / m, r0 = r % m, l1, r1, mid, res0 = 0;
    
if (S0 == S1) re3(i, l0, r0) {if (A[S0 * m + i] < x) res0++;} else {
        re2(i, l0, LEN[S0]) 
if (A[S0 * m + i] < x) res0++;
        re3(i, 
0, r0) if (A[S1 * m + i] < x) res0++;
        re2(i, S0
+1, S1) {
            l1 
= 0; r1 = LEN[i];
            
while (l1 < r1) {
                mid 
= l1 + r1 >> 1;
                
if (T[i][mid] >= x) r1 = mid; else l1 = mid + 1;
            }
            res0 
+= l1;
        }
    }
    
return res0;
}
int main()
{
    
int M, a0, b0, x0, l, r, mid; char ss[20];
    scanf(
"%d%d"&n, &M);
    re(i, n) scanf(
"%d"&A[i]); prepare();
    re(i, M) {
        scanf(
"%s", ss);
        
if (ss[0== 'C') {
            scanf(
"%d%d"&a0, &x0); opr0(--a0, x0);
        } 
else {
            scanf(
"%d%d%d"&a0, &b0, &x0); a0--; b0--;
            l 
= 0; r = 1000000000;
            
while (l < r) {
                mid 
= l + r + 1 >> 1;
                
if (opr1(a0, b0, mid) < x0) l = mid; else r = mid - 1;
            }
            printf(
"%d\n", l);
        }
    }
    
return 0;
}



 

posted @ 2012-01-17 20:31 Mato_No1 阅读(1113) | 评论 (2)编辑 收藏

【1】新型LCA算法:(在WJMZBMR神犇空间上发现的,系神犇自创,Orz!!!)
这种算法可以在仅使用树的路径剖分预处理中求出的DEP和UP来求任意两点的LCA,时间复杂度为O(log2N),不需要单独的预处理。
步骤(假设求a0、b0两点的LCA):
(1)若UP[a0]==UP[b0],则a0、b0位于同一条重链上,显然a0、b0中深度小的那个就是LCA了,返回结果,结束;
(2)若UP[a0]!=UP[b0]且DEP[UP[a0]]>=DEP[UP[b0]],则LCA不可能在a0所在的那条重链上。证明:若LCA在a0所在的重链上,则UP[a0]必然也是a0、b0的公共祖先,也就是UP[a0]是b0的祖先。由于UP[a0]的深度大于等于UP[b0],若DEP[UP[a0]]>DEP[b0],则UP[a0]显然不可能是b0的祖先,否则,在b0所在的重链上必然存在一个点C,满足DEP[C]=DEP[UP[a0]],显然,C也是b0的祖先,这就说明在树中同一深度处存在两个不同的结点,它们都是b0的祖先,这是不可能的,所以,LCA不可能在a0所在重链上。那么,a0就可以上溯到UP[a0]的父结点处(也就是E[FA[UP[a0]]].a),b0不动,然后继续判断;
(3)若UP[a0]!=UP[b0]且DEP[UP[a0]]<DEP[UP[b0]],则LCA不可能在b0所在的重链上,将b0上溯到E[FA[UP[b0]]].a,a0不动,继续判断。
由于a0、b0最多上溯O(log2N)次,所以该算法一定能在O(log2N)时间内求出LCA(a0, b0)。
该算法的应用很广,不光可以在树的路径剖分中快速求出LCA,精简代码,同时也减少了一些时间(因为它不需要像RMQ那样进行预处理),而且,在一般的求LCA问题中,也可以先剖分求出UP再求LCA。
代码:
int LCA(int a, int b)
{
    
while (1) {
        
if (UP[a] == UP[b]) return DEP[a] <= DEP[b] ? a : b;
        
else if (DEP[UP[a]] >= DEP[UP[b]]) a = E[FA[UP[a]]].a; else b = E[FA[UP[b]]].a;
    }
}

【2】树的路径剖分模板总结:
(1)预处理部分:由于采用新型LCA算法(注意,求LCA的过程写成专门的函数),所以,原来预处理部分的后3步都不需要了,也就是只要前3步:第一步建有根树求出FA、DEP;第二步求出SZ划分轻重边;第三步找重链建线段树求出UP、ord、tot和root。那些为了求RMQ而设置的数组也不需要了。
(2)操作部分:难点在于上溯过程和衔接。设待操作的路径为a0->b0(注意是有向的,无向的也可以当成有向的处理),LCA0=LCA(a0, b0);
对于点权型的树,a0->LCA0的过程需要包含LCA0,而b0->LCA0的过程不能包含LCA0。因此当b0==LCA0时,第二步应该什么事都不做,所以要特判;此外,如果N==1(树中只有一个结点),为了防止引用根的父结点,也需要直接特判掉,所以,上溯过程可以分以下4步:
<1>特判:若n=1(此时必然有a0==b0==0),直接操作0号结点,结束;
<2>(a0->LCA)若a0是父边是轻边的叶结点,则单独处理a0,最新点设为a0,a0跳到a0的父结点处开始,否则从a0开始(上溯)。上溯终止条件为DEP[a0]<DEP[LCA0]或者上溯到根结点,每次处理时,设c=”UP[a0]不超越LCA?UP[a0]:LCA",对[c, a0]段处理(l0=ord[c], r0=ord[a0]),再将a0上溯到c的父结点处(若c是根结点则退出);处理时,线段树中记录的所有有向的(从左到右的)信息都要反向;衔接时应不断向右衔接;
<3>(b0->LCA)与<2>类似,两个不同点:一是有向的信息不要反向,衔接时应不断向左衔接;二是若c==LCA,则l0=ord[c]+1;
<4>最后将<2>中和<3>中得到的两个衔接链再衔接一下即可;

对于边权型的树,a0->LCA0的过程和b0->LCA0的过程都要包含LCA0引出的边,b0==LCA0以及N==1时不需要特判(因为它们会自动地什么事都不做);在处理过程中,l0=ord[c], r0=ord[a0]-1;要分轻边和重链分别处理;每次a0上溯到c而不是c的父结点处;终止条件为DEP[a0]<=DEP[LCA0]。

模板题:PKU2831(动态最小生成树问题,需要涉及到最小生成树中两点之间路径上的最大边权,用树的路径剖分。其实本题有离线算法,不需要树的路径剖分)
#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
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++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
const int MAXN = 1001, MAXM = 100001, INF = ~0U >> 2;
struct _edge {
    
int a, b, w;
} _E[MAXM], _E2[MAXM];
struct edge {
    
int a, b, w, pre, next;
    
bool Z;
} E0[MAXN 
<< 2], E[MAXN << 2];
struct node {
    
int maxw, lch, rch;
} T[MAXN 
<< 2];
int n, _m, m0, m, N, u[MAXN], Q[MAXN], FA[MAXN], DEP[MAXN], SZ[MAXN], UP[MAXN], ord[MAXN], w0[MAXN], tot[MAXN], root[MAXN], l0, r0, x0, res;
bool vst[MAXN];
void init_d()
{
    re(i, n) E0[i].pre 
= E[i].pre = E0[i].next = E[i].next = i;
    m0 
= m = n;
}
void add_edge0(int a, int b, int w)
{
    E0[m0].a 
= a; E0[m0].b = b; E0[m0].w = w; 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].w = w; E0[m0].pre = E0[b].pre; E0[m0].next = b; E0[b].pre = m0; E0[E0[m0].pre].next = m0++;
}
void add_edge(int a, int b, int w)
{
    E[m].a 
= a; E[m].b = b; E[m].w = w; E[m].Z = 0; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m++;
}
int cmp(const void *s1, const void *s2)
{
    
return ((_edge *)s1)->- ((_edge *)s2)->w;
}
int UFS_find(int x)
{
    
int r = x, tmp; while (u[r] >= 0) r = u[r]; while (u[x] >= 0) {tmp = u[x]; u[x] = r; x = tmp;} return r;
}
void UFS_union(int x1, int x2)
{
    
if (u[x1] >= u[x2]) {u[x2] += u[x1]; u[x1] = x2;} else {u[x1] += u[x2]; u[x2] = x1;}
}
int mkt(int l, int r)
{
    
int No = ++N;
    
if (l == r) {T[No].maxw = w0[l]; T[No].lch = T[No].rch = 0;} else {
        
int mid = l + r >> 1, l_r = mkt(l, mid), r_r = mkt(mid + 1, r);
        T[No].maxw 
= T[T[No].lch = l_r].maxw >= T[T[No].rch = r_r].maxw ? T[l_r].maxw : T[r_r].maxw;
    }
    
return No;
}
void prepare()
{
    qsort(_E2, _m, 
sizeof(_E2[0]), cmp);
    re(i, n) u[i] 
= -1;
    
int a, b, r1, r2, total = 0, maxsz, x, n0;
    re(i, _m) {
        a 
= _E2[i].a; b = _E2[i].b; r1 = UFS_find(a); r2 = UFS_find(b);
        
if (r1 != r2) {UFS_union(r1, r2); add_edge0(a, b, _E2[i].w); if (++total == n - 1break;}
    }
    re(i, n) vst[i] 
= 0; Q[0= DEP[0= N = 0; vst[0= 1; FA[0= -1;
    
for (int front=0, rear=0; front<=rear; front++) {
        a 
= Q[front];
        
for (int p=E0[a].next; p != a; p=E0[p].next) {
            b 
= E0[p].b;
            
if (!vst[b]) {FA[b] = m; DEP[b] = DEP[a] + 1; vst[b] = 1; Q[++rear] = b; add_edge(a, b, E0[p].w);}
        }
    }
    rre(i, n) {
        a 
= Q[i]; SZ[a] = 1; maxsz = 0;
        
for (int p=E[a].next; p != a; p=E[p].next) {
            b 
= E[p].b; SZ[a] += SZ[b]; if (SZ[b] > maxsz) {maxsz = SZ[b]; x = p;}
        }
        
if (SZ[a] > 1) E[x].Z = 1;
    }
    UP[
0= ord[0= 0;
    re2(i, 
1, n) {
        a 
= Q[i]; int p = FA[a]; if (E[p].Z) {UP[a] = UP[E[p].a]; ord[a] = ord[E[p].a] + 1;} else {UP[a] = a; ord[a] = 0;}
        
if (SZ[a] == 1 && E[FA[a]].Z) {
            b 
= UP[a]; n0 = ord[a]; for (int j=a; j!=b; j=E[FA[j]].a) w0[--n0] = E[FA[j]].w;
            tot[b] 
= ord[a]; root[b] = mkt(0, ord[a] - 1);
            
for (int j=a; j!=b; j=E[FA[j]].a) {tot[j] = tot[b]; root[j] = root[b];}
        }
    }
}
int LCA(int a, int b)
{
    
while (1) {
        
if (UP[a] == UP[b]) return DEP[a] <= DEP[b] ? a : b;
        
else if (DEP[UP[a]] >= DEP[UP[b]]) a = E[FA[UP[a]]].a; else b = E[FA[UP[b]]].a;
    }
}
void opr0(int l, int r, int No)
{
    
if (l >= l0 && r <= r0) {if (T[No].maxw > res) res = T[No].maxw;} else {
        
int mid = l + r >> 1;
        
if (mid >= l0) opr0(l, mid, T[No].lch);
        
if (mid < r0) opr0(mid + 1, r, T[No].rch);
    }
}
int main()
{
    
int P, s, a0, b0, w0, LCA0, c;
    scanf(
"%d%d%d"&n, &_m, &P); init_d();
    re(i, _m) {
        scanf(
"%d%d%d"&a0, &b0, &w0);
        _E[i].a 
= _E2[i].a = --a0; _E[i].b = _E2[i].b = --b0; _E[i].w = _E2[i].w = w0;
    }
    prepare();
    re(i, P) {
        scanf(
"%d%d"&s, &w0); a0 = _E[--s].a; b0 = _E[s].b; LCA0 = LCA(a0, b0);
        res 
= -INF;
        
while (DEP[a0] > DEP[LCA0]) {
            
if (E[FA[a0]].Z) {
                
if (DEP[UP[a0]] >= DEP[LCA0]) c = UP[a0]; else c = LCA0;
                l0 
= ord[c]; r0 = ord[a0] - 1; opr0(0, tot[a0] - 1, root[a0]); a0 = c;
            } 
else {
                
if (E[FA[a0]].w > res) res = E[FA[a0]].w;
                a0 
= E[FA[a0]].a;
            }
        }
        
while (DEP[b0] > DEP[LCA0]) {
            
if (E[FA[b0]].Z) {
                
if (DEP[UP[b0]] >= DEP[LCA0]) c = UP[b0]; else c = LCA0;
                l0 
= ord[c]; r0 = ord[b0] - 1; opr0(0, tot[b0] - 1, root[b0]); b0 = c;
            } 
else {
                
if (E[FA[b0]].w > res) res = E[FA[b0]].w;
                b0 
= E[FA[b0]].a;
            }
        }
        puts(res 
>= w0 ? "Yes" : "No");
    }
    
return 0;
}

好了,对于模板也就到此为止了,接下来该搞应用了。

posted @ 2012-01-14 12:34 Mato_No1 阅读(1118) | 评论 (1)编辑 收藏

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