摘要: 原题地址本题就是,有N个变量,其取值只可能有三种:1、2、3。现在已知它们之间的一些大小关系,求有多少对变量(I, J)满足(I的值+J的值)一定>或=或<(A的值+B的值),A、B是指定的两个变量,且I、J、A、B互不相同。首先,对于值相等的变量,直接建出无向图,按连通块缩点,再考虑大于/小于关系,如果I的值大于J则在I所在连通块与J所在连通块之间连一条有向边(从I到J)…...  阅读全文

posted @ 2012-09-25 21:26 Mato_No1 阅读(716) | 评论 (0)编辑 收藏

RQNOJ187 巧置挡板
———————————————————————————————————————————————————
【背景(神犇不要囧视,3x)】
本沙茶最近开始猛攻搜索、近似、随机等算法,目标是A掉AHOI2012的后3题(在哪跌倒就从哪爬起)。昨天晚上找到这题,发现是搜索,于是开始捉……结果被折腾了一晚上加一上午才A掉,不过,看在这题可以系统性的反映DFS优化的一般步骤,忍了……另外,这题是本沙茶在RQNOJ上通过的第400题……回想起通过第300题的时候已经是近三年半之前了……真颓废啊……
———————————————————————————————————————————————————
普通DFS(不加迭代)的优化方法主要有:
(1)可行性剪枝,如果遇到已经无法产生可行解的情况就剪枝,有时候,可行性剪枝也可以指在搜索过程中对不合法情况的剔除;
(2)最优性剪枝:如果遇到已经无法产生比目前得到的最优解还要优的解的情况就剪枝,这其中包括启发式最优性剪枝(即分支限界),对目前解的进展情况作乐观估计,如果估计值都不能超越目前的最优解,则剪枝;
(3)通过改变搜索顺序,尽早得出较优解,从而为后面的剪枝提供余地;
(4)通过预处理等手段,提前得到启发值或者较优解,为后面的剪枝提供余地;

一般步骤:
(1)预处理,当然是写在最前面,在搜索前得到启发值等东东;
(2)搜索过程中,先写最优性剪枝(包括已经到达搜索终点了,也应该判断一下,提前排除不是最优解的情况);
(3)再判定搜索终点,如果到了,也不要马上更新解,而是要判定这个解是否符合要求,再更新;
(4)若未到终点,再写可行性剪枝;
(5)最后写搜索过程,包括需要改变搜索顺序的情况。

注意事项:数组的分层问题。
这是一个很严重的问题,因为分层出错很可能会导致一些很怪的错误出现,难以发现。在搜索题的调试技巧这一篇中,已经提到了这个问题,本题又涉及到了。
一般来说,搜索过程中使用的数组(包括变量,可以视为零维数组)可以分为以下三类:
(1)在搜索过程中,只会引用其值,不会修改的数组(比如预处理中得到的那些东东),相当于常量;
(2)在搜索过程中,会改变其值,但在搜索完毕后能改回原值的数组;
(3)在搜索过程中,会改变其值,且在搜索完毕后也改不回原值的数组。
对于(1)(2)类数组,不需分层,但对于第(3)类数组一定要分层。这是因为这些数组在多层搜索中都需要修改,而且搜索完了也改不回来,如果不分层,则当后面的搜索完毕以后,要继续搜索前面的,引用的将是后面的值,就会出错。
对于第(3)类数组,有的已经“自然分层”(每层搜索修改的元素都不一样),比如本题代码中的R[][]、C[][]。然而有的并没有自然分层,比如本题的len、pos[]、tmp[]等,因此对于这些数组,需要再加一维,对于不同层使用该维不同的元素即可。

下面是本题的算法:
使用DFS搜索每个位置的挡板是否需要放。搜索时,先搜竖的再搜横的,由于题目要求每个连通块都必须是矩形,因此对于竖的,如果该位置的上方两个相邻位置的横的没有全放(包括只放了一个),则该位置的竖的放不放取决于其上一行对应位置的竖的有没有放(第一行除外),如果两个横的都放了,则这里的竖的既可以放也可以不放(先搜不放的情况)。当然,一行的两个1之间必须至少有一个竖的,这是可行性限制。在搜横的的时候,按照该行所放的竖的,分成若干段,显然每一段要么下面都用横的封上,要么一个都不封上。其中,如果该段所在的连通块里面有1,且下一行对应位置也有1,则必须封上;若该段所在连通块里面没有1,则不能封上(因为不能有一个连通块里一个1都没有),否则可以自由选择封还是不封(当然也是先搜不封的)。这样一直搜到最后一行的竖的后,还要判断最后一行有没有连通块里没1,若没有,则为一个可行解。启发式优化:设D[i]为第i行及以后至少要几个挡板(若某行有K个1,则至少需要(K-1)个竖的;若某列有K个1,则至少需要(K-1)个横的,累加起来即可,显然这是乐观估计),D[i]可以预处理出来,当做启发值。

代码:
#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 = 35, INF = ~0U >> 2;
int n, m, D[MAXN], len[MAXN], pos[MAXN][MAXN], tmp[MAXN][MAXN], res = INF;
bool A[MAXN][MAXN], C[MAXN][MAXN], R[MAXN][MAXN], S[MAXN][MAXN][MAXN][MAXN];
void dfs0(int No, int v, int sum, bool FF);
void init()
{
    scanf(
"%d%d"&n, &m); int x;
    re(i, n) re(j, m) {scanf(
"%d"&x); A[i][j] = x;}
}
void prepare()
{
    re(i, n) re(j, m) {
        S[i][j][i][j] 
= A[i][j];
        re2(k, i
+1, n) S[i][j][k][j] = A[k][j] || S[i][j][k - 1][j];
        re2(k, j
+1, m) S[i][j][i][k] = A[i][k] || S[i][j][i][k - 1];
        re2(k, i
+1, n) re2(k0, j+1, m) S[i][j][k][k0] = A[k][k0] || S[i][j][k - 1][k0] || S[i][j][k][k0 - 1];
    }
    
int sum;
    re(i, n) {
        D[i] 
= 0;
        re2(j, i, n) {sum 
= 0; re(k, m) if (A[j][k]) sum++if (sum) D[i] += sum - 1;}
        re(k, m) {sum 
= 0; re2(j, i, n) if (A[j][k]) sum++if (sum) D[i] += sum - 1;}
    }
}
void dfs1(int No, int v, int sum)
{
    
if (sum + D[No + 1>= res) return;
    
if (v == len[No] + 1) dfs0(No + 10, sum, 1); else if (tmp[No][v] != 1) dfs1(No, v + 1, sum); else {
        
int l, r, sum0 = sum;
        
if (v) l = pos[No][v - 1+ 1else l = 0;
        
if (v == len[No]) r = m - 1else r = pos[No][v];
        re3(j, l, r) R[No][j] 
= 0; dfs1(No, v + 1, sum);
        re3(j, l, r) {R[No][j] 
= 1; sum0++;} dfs1(No, v + 1, sum0);
    }
}
void dfs0(int No, int v, int sum, bool FF)
{
    
if (sum + D[No + 1>= res) return;
    
bool FF0; if (A[No][v]) {if (!FF) returnelse FF0 = 0;} else FF0 = FF;
    
if (v == m - 1) {
        
if (No == n - 1) {
            len[No] 
= 0; re(i, m-1if (C[No][i]) pos[No][len[No]++= i;
            
int l, r, x; bool FFF = 1;
            re3(i, 
0, len[No]) {
                
if (i) l = pos[No][i - 1+ 1else l = 0;
                
if (i == len[No]) r = m - 1else r = pos[No][i];
                x 
= 0; rre(j, No) if (R[j][l]) {x = j + 1break;}
                
if (!S[x][l][No][r]) {FFF = 0break;}
            }
            
if (FFF) res = sum;
            
return;
        }
        len[No] 
= 0; re(i, m-1if (C[No][i]) pos[No][len[No]++= i;
        
int l, r, x, sum0 = sum;
        re3(i, 
0, len[No]) {
            
if (i) l = pos[No][i - 1+ 1else l = 0;
            
if (i == len[No]) r = m - 1else r = pos[No][i];
            x 
= 0; rre(j, No) if (R[j][l]) {x = j + 1break;}
            
if (S[x][l][No][r] && S[No + 1][l][No + 1][r]) {
                tmp[No][i] 
= 2; re3(j, l, r) {sum0++; R[No][j] = 1;}
            } 
else if (S[x][l][No][r]) {
                tmp[No][i] 
= 1; re3(j, l, r) R[No][j] = 0;
            } 
else {
                tmp[No][i] 
= 0; re3(j, l, r) R[No][j] = 0;
            }
        }
        dfs1(No, 
0, sum0);
    } 
else if (No && (!R[No - 1][v] || !R[No - 1][v + 1])) {
        
if (C[No - 1][v]) {C[No][v] = 1; dfs0(No, v + 1, sum + 11);} else {C[No][v] = 0; dfs0(No, v + 1, sum, FF0);}
    } 
else {
        C[No][v] 
= 0; dfs0(No, v + 1, sum, FF0);
        C[No][v] 
= 1; dfs0(No, v + 1, sum + 11);
    }
}
void pri()
{
    printf(
"%d\n", res);
}
int main()
{
    init();
    prepare();
    dfs0(
0001);
    pri();
    
return 0;
}


posted @ 2012-09-23 15:16 Mato_No1 阅读(840) | 评论 (0)编辑 收藏

[SCOI2007 kshort]
求图的s-t第K短简单路问题,若有长度相同的,字典序小的优先。

首先,由于是简单路,所以A*是不能做的,因为有可能有两条s-i(i为某个中间点)路P1和P2,P1比P2短,但由于P1到达的顶点与P2不同,导致最终沿P1到达t的路径长度长于沿P2到达t的(甚至有可能沿P1根本到不了t)。然后,如果直接用DFS,由于要求的是第K优解,而不是最优解,所以不能使用最优性剪枝(包括分支限界),因此专门为最优性剪枝服务的“改变搜索顺序”技巧也不能使用了,因此,能够使用的只有可行性剪枝,而本题的数据范围使得这种算法必然TLE。

正解是一种由迭代加深思想扩展得到的“迭代变优”DFS。设置一个路径长度上限Z,搜索s到t的所有简单路,在搜索过程中,遇到长度大于Z的路径就停止(剪枝),然后,若得到路径不足K条,则增加Z的值,重新开始搜索,直到得到的路径总数大于等于K条为止。这里可以进行启发式的优化,设g[i]为点i到t的最短路长度,则搜索过程中,假设目前搜到点i,则(目前路径长度+g[i])是对整条路径最短长度的乐观估计,如果这个值超过了Z,就可以剪枝,但在剪枝之前要记下这个超过了Z的启发值,取其中最小的作为下一次迭代的Z值。那么对于字典序肿么办?可以在搜索过程中,强制先搜编号小的结点,这样得到的s-t路径必然是字典序递增的。另外只要求出第K条路径,搜索就可以终止,因为容易证明,题目要求的第K短的路径一定已经求出来了(只不过不一定是最后一条而已),找到即可。此外,在搜索过程中不要忘了可行性剪枝,就是如果沿目前搜到的路径已经到不了t了,剪枝。

“迭代变优”DFS,就是设置一个解的评价值的上限(最小值)或下限(最大值),在搜索过程中,如果实际评价值(或者启发值,如果可以加启发式优化的话)越过这个限制,则剪枝。在一次搜索后,如果没有得到符合要求的解,就将该限制值设为本次搜索过程中越界“越”得最近的那个值,重新开始搜索,直到找到所需要的解或者发现无解(如果一次搜索中没有发生越界,却仍然没有找到解)为止。其应用主要体现在两个方面:(1)搜索树过深甚至无限深,但所需求的那个解却不深的情况;(2)求第K优解的情况。

代码:
#include <iostream>
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.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++)
#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 = 51, MAXK = 201, MAXM = 3000, INF = ~0U >> 2;
struct edge {
    
int a, b, w, pre, next;
} E[MAXM 
+ MAXN], E0[MAXM + MAXN];
struct edge0 {
    
int a, b, w;
    
bool operator< (edge0 e0) const {return b < e0.b;}
} _E[MAXM];
int n, m, s, t, K, dist[MAXN], Q[MAXN + 1];
int Z, Z0, vst0[MAXN], _FL, len0, X00[MAXN], No, len[MAXK], X0[MAXK][MAXN], sum0[MAXK], reslen, res[MAXN];
bool vst[MAXN], res_ex = 0;
void init_d()
{
    re(i, n) E[i].pre 
= E[i].next = E0[i].pre = E0[i].next = i; m = n;
}
void add_edge(int a, int b, int w)
{
    E[m].a 
= a; E[m].b = b; E[m].w = w; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m;
    E0[m].a 
= b; E0[m].b = a; E0[m].w = w; E0[m].pre = E0[b].pre; E0[m].next = b; E0[b].pre = m; E0[E0[m].pre].next = m++;
}
void init()
{
    
int m0; scanf("%d%d%d%d%d"&n, &m0, &K, &s, &t); init_d(); s--; t--;
    re(i, m0) {scanf(
"%d%d%d"&_E[i].a, &_E[i].b, &_E[i].w); _E[i].a--; _E[i].b--;}
    sort(_E, _E 
+ m0);
    re(i, m0) add_edge(_E[i].a, _E[i].b, _E[i].w);
}
void prepare()
{
    re(i, n) {vst[i] 
= 0; dist[i] = INF;} vst[t] = 1; dist[t] = 0; Q[0= t;
    
int x, y, d0, d1;
    
for (int front=0, rear=0!(!front && rear==|| front==rear+1); front==? front=0 : front++) {
        x 
= Q[front]; d0 = dist[x];
        
for (int p=E0[x].next; p != x; p=E0[p].next) {
            y 
= E0[p].b; d1 = d0 + E0[p].w;
            
if (d1 < dist[y]) {
                dist[y] 
= d1;
                
if (!vst[y]) {vst[y] = 1; Q[rear==? rear=0 : ++rear] = y;}
            }
        }
        vst[x] 
= 0;
    }
}
void dfs(int x, int sum)
{
    
if (x == t) {
        
if (sum <= Z) {sum0[No] = sum; len[No] = len0; re(i, len0) X0[No][i] = X00[i]; No++if (No == K) res_ex = 1;}
        
else if (sum < Z0) Z0 = sum;
        
return;
    } 
else {
        
int h0 = sum + dist[x];
        
if (h0 > Z) {if (h0 < Z0) Z0 = h0; return;}
        vst0[x] 
= ++_FL; Q[0= x; int _x, _y;
        
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;
                
if (!vst[_y] && vst0[_y] != _FL) {vst0[_y] = _FL; Q[++rear] = _y;}
            }
        }
        
if (vst0[t] != _FL) return;
        
for (int p=E[x].next; p != x; p=E[p].next) {
            _y 
= E[p].b;
            
if (!vst[_y]) {vst[_y] = 1; X00[len0++= _y; dfs(_y, sum + E[p].w); if (res_ex) returnelse {len0--; vst[_y] = 0;}}
        }
    }
}
void solve()
{
    Z 
= dist[s]; int No0 = 0; _FL = 0;
    
while (1) {
        Z0 
= INF; No = 0; re(i, n) {vst[i] = 0; vst0[i] = 0;}
        vst[s] 
= 1; len0 = 1; X00[0= s; dfs(s, 0);
        
if (res_ex) {
            No0 
= K - No0;
            re(i, K) 
if (sum0[i] == Z) {No0--if (!No0) {reslen = len[i]; re(j, len[i]) res[j] = X0[i][j];}}
            
break;
        } 
else if (Z0 == INF) breakelse {No0 = No; Z = Z0;}
    }
}
void pri()
{
    
if (res_ex) {
        printf(
"%d", res[0+ 1);
        re2(i, 
1, reslen) printf("-%d", res[i] + 1);
        puts(
"");
    } 
else puts("No");
}
int main()
{
    init();
    prepare();
    solve();
    pri();
    
return 0;
}


posted @ 2012-09-23 14:28 Mato_No1 阅读(1447) | 评论 (0)编辑 收藏

原题地址
典型的二次递推/DP的题目。
首先,题目中的“不便利值”指的是某个点到根的路径上的木有被选定链覆盖的边的条数。

第一问:设F[i][0..2]分别为当子树i中结点i的状态为不参与链(0)、作为某链端点(1)、作为某链中间点(2)时,子树i中的结点到i的最小不便利值。为了得到F,需要设立G[j][k(0..2)]表示结点i的前j棵子树中,有k棵的根结点与结点i接上的最小的最大不便利值。显然,不和i接上的,状态为0、1、2都行,但不便利值要加1,而和i接上的状态只能是0或1,不加1。

问题是第二问。第二问的难点在于,当i取得最小不便利值时,i的每个子结点并非都取到最小不便利值。举个例子,结点i的最小不便利值为3,它的某个子结点j的最小不便利值为2,则当j与i接上时,子树j的内部既可以取不便利值为2的解,也可以取不便利值为3的解。所以,为了解决第二问,需要求出结点i的最小不便利值为x的解的总数。万幸的是,x的范围并不是太大,可以证明,x不会超过log3N(下取整),也就是当N=100000时x最大为10。因此,最后仍然不会T掉。

这题的一个启示就是,在求类似于“最优解计数”的问题中,不要认为当后面的状态取得最优解时,前面的状态一定取得最优解。因此,不能只记录某状态取得最优解的个数,而要记录该状态取得每一个可行解时的个数。

代码:
#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 = 100010, MAXW = 11, INF = ~0U >> 2;
struct edge {
    
int a, b, pre, next;
} E0[MAXN 
* 3], E[MAXN << 1];
int n, m0, m, Q[MAXN], F[MAXN][3], G[MAXN][3], res1 = 0;
ll MOD, FS[MAXN][MAXW][
3], S[MAXN][MAXW][3], res2 = 0;
bool vst[MAXN];
void init_d()
{
    re(i, n) E0[i].pre 
= E0[i].next = E[i].pre = E[i].next = i; m0 = m = n;
}
void add_edge0(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++;
    E0[m0].a 
= b; E0[m0].b = a; 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)
{
    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()
{
    
int _M; scanf("%d%d"&n, &_M); cin >> MOD; if (_M < n - 1) {res1 = res2 = -1return;} init_d(); int a0, b0;
    re2(i, 
1, n) {scanf("%d%d"&a0, &b0); add_edge0(--a0, --b0);}
}
void prepare()
{
    re(i, n) vst[i] 
= 0; Q[0= 0; vst[0= 1int x, y;
    
for (int front=0, rear=0; front<=rear; front++) {
        x 
= Q[front];
        
for (int p=E0[x].next; p != x; p=E0[p].next) {
            y 
= E0[p].b;
            
if (!vst[y]) {vst[y] = 1; Q[++rear] = y; add_edge(x, y);}
        }
    }
    re(i, n) 
if (!vst[i]) {res1 = -1; res2 = -1return;}
}
inline 
int minv3(int s1, int s2, int s3)
{
    
int s0 = s1 <= s2 ? s1 : s2;
    
return s0 <= s3 ? s0 : s3;
}
inline 
int minv2(int s1, int s2)
{
    
return s1 <= s2 ? s1 : s2;
}
void solve()
{
    
int x, y, len, v1, v2, v01, v02; ll sum;
    rre(i, n) {
        x 
= Q[i]; len = 0; G[0][0= 0; G[0][1= G[0][2= INF;
        
for (int p=E[x].next; p != x; p=E[p].next) {
            y 
= E[p].b; len++;
            v1 
= minv3(F[y][0], F[y][1], F[y][2]) + 1; v2 = minv2(F[y][0], F[y][1]);
            G[len][
0= v1 >= G[len - 1][0? v1 : G[len - 1][0];
            v01 
= v1 >= G[len - 1][1? v1 : G[len - 1][1];
            v02 
= v2 >= G[len - 1][0? v2 : G[len - 1][0];
            G[len][
1= minv2(v01, v02);
            v01 
= v1 >= G[len - 1][2? v1 : G[len - 1][2];
            v02 
= v2 >= G[len - 1][1? v2 : G[len - 1][1];
            G[len][
2= minv2(v01, v02);
        }
        re(j, 
3) F[x][j] = G[len][j];
        re(j, MAXW) {S[
0][j][0= 1; S[0][j][1= S[0][j][2= 0;} len = 0;
        
for (int p=E[x].next; p != x; p=E[p].next) {
            y 
= E[p].b; len++;
            re(j, MAXW) re(k, 
3) {
                S[len][j][k] 
= 0;
                
if (j) {
                    sum 
= 0; re(k0, 3) {sum += FS[y][j - 1][k0]; if (sum >= MOD) sum -= MOD;}
                    S[len][j][k] 
= (sum * S[len - 1][j][k]) % MOD;
                }
                
if (k) {
                    sum 
= 0; re(k0, 2) {sum += FS[y][j][k0]; if (sum >= MOD) sum -= MOD;}
                    S[len][j][k] 
= (S[len][j][k] + sum * S[len - 1][j][k - 1]) % MOD;
                }
            }
        }
        re(j, MAXW) re(k, 
3) FS[x][j][k] = S[len][j][k];
    }
    res1 
= minv3(F[0][0], F[0][1], F[0][2]);
    res2 
= 0; re(i, 3if (F[0][i] == res1) res2 += FS[0][F[0][i]][i]; res2 %= MOD;
}
void pri()
{
    cout 
<< res1 << endl << res2 << endl;
}
int main()
{
    init();
    
if (!res1) prepare();
    
if (!res1) solve();
    pri();
    
return 0;
}


posted @ 2012-09-22 16:21 Mato_No1 阅读(790) | 评论 (0)编辑 收藏

相关链接

由此看来,省队(准确来说是“省集训队”)的压力减小很多了囧……因为即使省选题目再坑爹,也有NOIP的40%垫着……
显然,NOIP必须得高分,甩开别人,甩得越远越好,这样才能在省集训队的选拔中占据优势……
不过,进了省集训队之后又肿么选,就不知道了……

而且估计明年的省选应该木有这么坑爹了囧……
其实……最近发现即使是AHOI2012的后3题,只要会各种搜索+近似也是可以搞到很多分的……至少加起来100分木问题(总分200就A队了)……

所以,实力终究是硬道理!!!!!!!!!!!!!!!!!!!!!!!!!!!!

我要进省队!!!!
我要进国家集训队!!!!

posted @ 2012-09-21 21:34 Mato_No1 阅读(550) | 评论 (0)编辑 收藏

最近做了两道LIS模型题,感觉到模型比较好,总结一下囧。
【1】[HAOI2007]上升序列
预处理:设F[i]为以i开头的最长上升序列的长度,怎么求不用说了吧囧……
假设目前需要求长度为M的、标号字典序最小的上升序列,显然其第一个元素A[i]必须满足F[i]>=M(注意,不是等于,是大于等于!),找到满足这个条件的最小的i即可。然后,设目前已经求出了该序列的第x个元素为A[y],则第(x+1)个元素A[z]需要满足的条件是A[z]>A[y],且F[z]=F[y]-1,找到满足这个条件的最小的z即为该序列的第(x+1)个元素。按照这种方法,扫描一遍就可以求出整个序列,时间复杂度为O(N)。如果整个序列的最长上升序列长度<M,则无解。

代码:
#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 = 10010,    MAXM = 1010, INF = ~0U >> 2;
int n, m, len, A[MAXN], F[MAXN], D[MAXN], res[MAXM];
void prepare()
{
    D[len 
= 0= INF; int l, r, mid;
    rre(i, n) 
if (A[i] < D[len]) D[F[i] = ++len] = A[i]; else {
        l 
= 0; r = len;
        
while (l < r) {
            mid 
= l + r + 1 >> 1;
            
if (A[i] < D[mid]) l = mid; else r = mid - 1;
        }
        F[i] 
= l + 1; D[l + 1= A[i];
    }
}
void solve()
{
    
int x, y;
    re(i, n) 
if (F[i] >= m) {
        res[
0= A[i]; if (m == 1return; x = m - 1; y = 1;
        re2(j, i
+1, n) if (F[j] >= x && A[j] > res[y - 1]) {res[y++= A[j]; if (y == m) returnelse x--;}
    }
}
int main()
{
    scanf(
"%d"&n); re(i, n) scanf("%d"&A[i]);
    prepare();
    
int m_s; scanf("%d"&m_s);
    re(i, m_s) {scanf(
"%d"&m); if (m > len) puts("Impossible"); else {solve(); re(j, m-1) printf("%d ", res[j]); printf("%d\n", res[m - 1]);}}
    
return 0;
}


【2】[HAOI2006]数字序列
首先,由于序列的所有元素都是整数,所以可以将原序列的所有元素减去它的下标,这样就把上升序列转化为不下降序列了。
第一问的结果显然就是(N-新序列的最长不下降序列长度)。关键在于第二问。以下A均表示新序列。
设F[i]为以A[i]结尾的最长不下降序列长度(同样,求法不用说了),G[i]为在A[i]不修改的前提下将A[0..i]转变为不下降序列的最小修改量。首先求出F[i],然后在求G[i]时,枚举上一个“不动点”(就是不修改的元素)A[j](显然必须满足A[j]<=A[i]且F[j]=F[i]-1),这样最小修改量就是G[j]+(将A[j..i]转变为不下降序列的最小修改量)。可以证明,A[j..i]的最优修改方案必然是将A[j+1..t]全部修改为A[j],A[t+1..i]全部修改为A[i],这里t是一个[j..i]范围的值。问题就是如何求出最优的t?
一开始,假设t=j,即把A[j+1..i-1]全部修改为A[i],计算出修改量,设为S。然后,由于A[j+1..i-1]之间的元素要么小于A[j],要么大于A[i](这个是显然的囧),我们把小于A[j]的元素称为“小数”,把大于A[i]的元素称为“大数”,则当t取t0时,修改量为S-(A[i]-A[j])*(A[j+1..t0]中的“小数”个数减去“大数”个数)。这样,只需扫描一下,求出使得(A[j+1..t0]中的“小数”个数减去“大数”个数)值最大的t0即可。
当然还有一个问题,对于同一个i,满足“A[j]<=A[i]且F[j]=F[i]-1”的元素个数可能有很多,如果一个一个枚举,一个一个扫描,会很慢的囧……解决方法是,求出满足这个条件的j中最小的一个,设为j0,然后把A[j0+1..i-1]中的所有“小数”和“大数”全部处理出来,然后用类似前缀和的方法就能搞了囧……当然,为了找到j0,需要建一个二分图,边为(F[i], i)。
最后,为了方便,可以把A序列的左边加一个-INF,右边加一个+INF。最后总的时间复杂度,理论上为O(N2),但由于是随机数据,所以远远达不到这个级别。

代码:
#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 = 40010, INF = ~0U >> 2;
struct edge {
    
int a, b, pre, next;
} E[MAXN 
<< 1];
int n, m, A[MAXN], D[MAXN], F[MAXN], W[MAXN], res1;
ll G[MAXN], res2;
void init_d()
{
    re(i, n) E[i].pre 
= E[i].next = i; m = n;
}
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);
    A[
0= -INF; re1(i, n) {scanf("%d"&A[i]); A[i] -= i;} A[++n] = INF; n++;
}
void solve()
{
    init_d(); F[
0= 0; G[0= 0; D[0= -INF; add_edge(00); int len = 0, l, r, mid, x, maxw; ll sum, tmp;
    re2(i, 
1, n) {
        
if (A[i] >= D[len]) D[F[i] = ++len] = A[i]; else {
            l 
= 0; r = len;
            
while (l < r) {
                mid 
= l + r + 1 >> 1;
                
if (A[i] >= D[mid]) l = mid; else r = mid - 1;
            }
            D[F[i] 
= ++l] = A[i];
        }
        
for (int p=E[F[i]-1].next; ; p=E[p].next) if (A[i] >= A[x = E[p].b]) break;
        W[x] 
= 0; re2(j, x+1, i) if (A[j] < A[i]) W[j] = W[j - 1+ 1else W[j] = W[j - 1- 1;
        sum 
= 0; maxw = -INF; G[i] = ~0Ull >> 2;
        rre2(j, i, x) {
            
if (A[j] <= A[i] && F[j] == F[i] - 1) {
                tmp 
= G[j] + sum; if (tmp < G[i]) G[i] = tmp;
                tmp 
= G[j] + sum - (ll) (maxw - W[j]) * (A[i] - A[j]); if (tmp < G[i]) G[i] = tmp;
            }
            
if (A[j] > A[i]) sum += A[j] - A[i]; else sum += A[i] - A[j];
            
if (W[j] > maxw) maxw = W[j];
        }
        add_edge(F[i], i);
    }
    res1 
= n - F[n - 1- 1; res2 = G[n - 1];
}
void pri()
{
    cout 
<< res1 << endl << res2 << endl;
}
int main()
{
    init();
    solve();
    pri();
    
return 0;
}


posted @ 2012-09-08 20:40 Mato_No1 阅读(1422) | 评论 (2)编辑 收藏

     摘要: 原题地址这个算法是由本沙茶在现场使用的那个做法扩展得来的……其实AC不了,后两个点会因为常数过大而T掉……但在BZOJ上算总时间的话能AC……首先考虑树的情形。设F[i]为从点i开始,往子树i里面走,到达叶结点的期望长度,则很容易得到递推公式:F[i] = (ΣF[j] + W(i, j)) / K,其中j是i的子结...  阅读全文

posted @ 2012-09-08 19:27 Mato_No1 阅读(388) | 评论 (0)编辑 收藏

原题地址
本沙茶的第一个无向环套树模型,纪念一下……

环套树,指的是每个连通块中点数都等于边数的无向图(称为无向环套树)或者是每个点有且只有一个前趋(称为内向环套树)或后继(称为外向环套树)的有向图,由于这个图的每个连通块当中有且只有一个环(注意,可能是自环,即长度为1的环),且这个环上的每个点都可以当作根引出一棵树,所以叫“环套树”。

对于无向环套树,先将整个图进行一次DFS,当中如果发现有逆向边(这条边第一次被发现必然是作为逆向边的,也就是起点是终点的后代),就说明找到了这个环,记录其起点和终点(注意,如果有多个连通块的话,不能退出,要继续遍历完),再不断上溯(因此在DFS过程中当然要记录父边了囧),就可以找到整个环了,然后再以环上的结点为根建树即可,这样依次处理每个连通块。

对于内向环套树(外向类似),找环更为简单,只需要任选一个点,不断去找它的前趋,同时记录找到的点序列,直到某个点在序列中出现两次为止,此时这个点以及序列中它两次出现的位置中间的所有点,就是环上的点,顺序也顺便记录下来,然后树也不用建了,直接在原图中找就行了。

对于这题,由于每个点都有且只有一个后继,所以是外向环套树,不过本沙茶更倾向于它的基图(无向图,是无向环套树),然后就是一个DP了囧……

代码:
#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 = 1000010;
const ll INF = ~0Ull >> 2;
struct edge {
    
int a, b, pre, next;
} E0[MAXN 
* 3], E[MAXN << 1];
int n, m0, m, A[MAXN], stk[MAXN], st[MAXN], pr[MAXN], Q[MAXN];
ll F[MAXN][
2], res = 0;
bool vst[MAXN], vst0[MAXN];
void init_d()
{
    re(i, n) E0[i].pre 
= E0[i].next = E[i].pre = E[i].next = i; if (n & 1) m0 = n + 1else m0 = n; m = n;
}
void add_edge0(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++;
    E0[m0].a 
= b; E0[m0].b = a; 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)
{
    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); int x; init_d();
    re(i, n) {
        scanf(
"%d%d"&A[i], &x); add_edge0(--x, i);
    }
}
void sol0(int x)
{
    Q[
0= x; int i, j, front, rear;
    
for (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 (!vst0[j]) {Q[++rear] = j; vst0[j] = 1; add_edge(i, j);}
        }
    }
    rre3(z, rear, 
0) {
        i 
= Q[z];
        F[i][
0= 0; F[i][1= A[i];
        
for (int p=E[i].next; p != i; p=E[p].next) {
            j 
= E[p].b; F[i][0+= F[j][0>= F[j][1? F[j][0] : F[j][1]; F[i][1+= F[j][0];
        }
    }
}
void solve()
{
    re(i, n) {vst[i] 
= vst0[i] = 0; st[i] = E0[i].next;} int x, y, tp, x0, y0; bool FF, FF2; ll tmp0, tmp1, tmp00, tmp01, res0;
    re(i, n) 
if (!vst[i]) {
        stk[tp 
= 0= i; vst[i] = 1; FF2 = 0;
        
while (tp >= 0) {
            x 
= stk[tp]; FF = 0;
            
for (int p=st[x]; p != x; p=E0[p].next) {
                y 
= E0[p].b;
                
if (!vst[y]) {vst[y] = 1; stk[++tp] = y; pr[y] = p; st[x] = E0[p].next; FF = 1break;}
                
else if (p != (pr[x] ^ 1&& !FF2) {FF2 = 1; x0 = x; y0 = y;}
            }
            
if (!FF) tp--;
        }
        
if (FF2) {
            tp 
= 0; vst0[y0] = 1while (x0 != y0) {stk[tp++= x0; vst0[x0] = 1; x0 = E0[pr[x0]].a;} stk[tp++= y0;
            re(j, tp) sol0(stk[j]);
            tmp0 
= F[stk[0]][0]; tmp1 = -INF;
            re2(j, 
1, tp) {
                tmp00 
= (tmp0 >= tmp1 ? tmp0 : tmp1) + F[stk[j]][0];
                tmp01 
= tmp0 + F[stk[j]][1];
                tmp0 
= tmp00; tmp1 = tmp01;
            }
            res0 
= tmp0 >= tmp1 ? tmp0 : tmp1;
            tmp0 
= -INF; tmp1 = F[stk[0]][1];
            re2(j, 
1, tp) {
                tmp00 
= (tmp0 >= tmp1 ? tmp0 : tmp1) + F[stk[j]][0];
                tmp01 
= tmp0 + F[stk[j]][1];
                tmp0 
= tmp00; tmp1 = tmp01;
            }
            res 
+= res0 >= tmp0 ? res0 : tmp0;
        }
    }
}
void pri()
{
    cout 
<< res << endl;
}
int main()
{
    init();
    solve();
    pri();
    
return 0;
}


posted @ 2012-09-01 17:03 Mato_No1 阅读(1007) | 评论 (0)编辑 收藏

最近参加了N多模拟赛……现在统一总结一下。
那些有代表性的题目总结一下。

(1)Aug.16 Poetize 杯NOIP模拟赛 I
(竟然AK了,虐场虐得真爽)
第一题:容易发现如果新加入的那条边连接的是同一个连通块,结果乘2加1,如果是不同的连通块,结果不变。证明:如果新边(i, j)的是同一个连通块,则原来i到j必然有路径,设P为i到j的一条路径,则在加入新边以前,原图的所有满足条件的子图都可以对P异或后得到一个新的图,该图仅有i、j两个的度为奇数,其余点的度均为偶数,加入(i, j)之后得到一个新的满足条件的子图,所以乘2,注意(i, j)加上P也是一个满足条件的子图,所以加1;如果原来i和j不在同一个连通块中,那么必定不存在包含(i, j)的满足条件的子图(否则这个子图将(i, j)删掉后会得到两个顶点度数和为奇数的连通块,这是不可能的),所以不变;

(2)Aug.17 Clover 杯NOIP模拟赛 I
第一题:判断一个点是否在三角形(其实对于所有的凸多边形都是这样)时,不需要引射线,只需把三角形的三个顶点按逆时针排序,然后判断该点往三角形的三对相邻顶点(按逆时针)的叉积是否都非负就行了;
第三题:枚举起点,然后状态压缩DP,注意最后一个点必须压缩一下才能不MLE;

(3)Aug.18 Vijos复活邀请赛
第一题:比较WS的几何题。判断圆与边平行于坐标轴的矩形是否相交的时候,可以采用以下的简便方法:一个圆与一个边平行于坐标轴的矩形相交,当且仅当矩形的四个顶点至少有一个在圆内,或者圆的四个横纵坐标最值点(最上、最下、最左、最右的点)至少有一个在矩形内,或者圆心在矩形内。
第三题:主要难在s-t两条路径怎么找出来的问题,方法:以s为根建有根树,找到到t的路径后,将那条不在树中的边的两端点到根的路径上的所有边都标记上,然后,若这棵树中s-t路径上的所有边都没标记,则s-t只有一条路径,否则任选一条s-t路径上的标记边删掉,然后再以s为根建一次有根树,就可以找到第二条s-t路径了;

(4)Aug.19 [Vani有约会]杯邀请赛 II
第二题:本沙茶的80%做法有点另类,是状态压缩DP,因为对于N<=800的数据,只有2、3、5、7、11、13、17、19、23这9个质数可能出现两次以上,其余的都只会出现一次,所以建立10个容器(别忘了1),分别分配1和这9个质数,再对剩下的质数状态压缩即可(一开始只建了9个容器,结果N=27挂了);

(5)Aug.20 『Citric杯』NOIP提高组模拟赛 I
第一题:这也太太太坑爹了吧囧……居然在精度上做手脚(Orz @sillycross),注意用long double就行了,不过正解是变除法为乘法;

(6)Aug.21 squarefk NOIP模拟赛
第三题:对于一个这样的序列A[0..N-1]:0<=Ai<=Ui,设F(A)为(A的0元素的个数)^(A的所有非0元素的乘积),问题就是求所有A的F值之积。显然,指数是不能取模的,所以要设F[i][j][k]为前i位j个非0,底数为k的值,递推式还是很容易的囧;

(7)Aug.22 Clover 杯NOIP模拟赛 II
第二题:问的是无向图中两点间是否有且仅有一条路径的问题。首先肯定是判是否在同一连通块,然后,对每个连通块任意建一棵生成树,如果这两点在生成树上的路径上的每条边都是桥,显然只有一条路径(因为每条边都必须经过),否则,必有其它的路径(某条边不是桥,也就是可以不经过),所以求桥之后就转化为了一个经典模型了囧……注意求LCA用类似路径剖分的算法比较好写……
第三题:很容易想到递推,F[i][j]:用i个布条,最后一个颜色为j,总方案数(下面的不解释了),问题是,如果至少有一种颜色能和两个或两个以上的颜色相配,还不会有事,因为结果一定不会超过log2(1018),但如果每种颜色都最多只能和一种颜色相配肿么办?因此这个需要特判:首先那些不能和任何颜色相配的肯定只能长度为1,减掉,然后剩下的每种颜色开始的每个长度的旗帜都各有一个,除以颜色总数(上取整)即可。

(8)Aug.23 Poetize 杯NOIP模拟赛 II 暨Tyvj龙年七夕欢乐赛
(第一题正解是贪心,本沙茶用费用流乱搞,T了两个点)
第三题:A先mod B消去整数。首先考虑有限小数的情况。结果是有限小数时,设小数点后位数为M,则必有A*K^M mod B=0,显然这个方程有解的充要条件是B的每个质因数也都得是A*K的质因数,只要把B的质因数当中减掉A的,再看看剩下的质因数K是不是都有,都有的话剩下的就是乱搞一下了囧……如果不都有说明是循环小数,设混循环部分位数为M,循环节位数为R,则有A*(K^(M+R)-K^M) mod B = 0,整理得A*K^M*(K^R-1) mod B = 0,注意K^M与(K^R-1)是互质的,也就是把B的质因数中减掉A的之后,剩下的每个质因数,要么就是K有,要么就是(K^R-1)有。这样,可以用类似于有限小数的办法来求出M,对于剩下的(K没有的)质因数,设它们的积(包含指数)为W,则必有K^R mod W = 1,K和W互质,根据欧拉定理,phi(W)必然是一个解,但不一定是最小正整数解,不过,可以肯定的是,最小正整数解一定是phi(W)的因数(不一定是质因数!!),因为若最小正整数解R0不是phi(W)的因数,设phi(W)=P*R0+Q(0<Q<R0),因为K^(P*R0+Q) = (K^R0)^P * K^Q mod W = 1,而(K^R0)^P mod W显然也是1,所以必有K^Q mod W=1,这样就得到了一个比R0还小的正整数解Q,这与R0是最小正整数解矛盾。因此,枚举phi(W)的因数,再用快速幂判断即可。

(9)Aug.25 『Citric杯』NOIP提高组模拟赛 II
第一题:这也太太太太太太太太太太太太太太太太坑爹了吧囧……其实题目描述有漏洞的,因为题目中并木有说表示结束的询问一定是输入的最后一个……
第三题:这题本沙茶的做法巨另类也巨繁琐(就当字符串算法的综合复习了囧……),首先一头一尾两段的字符串还是很好找的……结尾的那段直接枚举长度,开头的的那段可以在整个字符串左边用一个类似KMP的办法搞(其实只要知道KMP的原理就能用它解决N多奇特问题了囧……),难点在于中间的那段字符串,需要是一个长度为奇数的回文串。为了快速找出一段连续子串里最长的长度为奇数的回文串,可以设G[i]为以i为中心的最长回文串长度,这可以将整个字符串逆序一下后接在原串后面(注意中间要加一个未出现的字符),然后用后缀数组解决(经典模型)。注意在找最长回文串的时候不能直接求中间G[i]的最大值,因为可能延伸出去,正解是二分枚举这个长度,然后在中间不会延伸出去的那一段里找G的最大值,看是否符合要求。总时间复杂度O(NlogN)。

(10)Aug.26 RQNOJ2012年八月月赛
第二题:比赛的时候本沙茶用单调队列硬搞搞不出来,后来写朴素了(悲剧)……正解是将所有的前缀和按照先值递增,再下标递减排序,并串成Dancing Link,然后从按下标从大到小依次删掉每个前缀和,这样,每个前缀和左边的那个一定是比值比它小的前缀和中值最大(值相同则下标最小)的那个,剩下就不解释了囧……

(11)Aug.28 「Clover」杯NOIP模拟赛 III
第三题:先把每条无向边拆成两条有向边,然后对这个有向图求源点为1的单源最短路径图(就是由所有满足D[i] + W<i, j> = D[j]的边<i, j>组成的图),显然从这个图的点1开始无论怎么走都是最短路径,而且这个图显然是无环的(因为原图中的每条边权值都是正数,说实话,如果不满足这个条件,这题就巨难做了,至少本沙茶不会做了),题目中要求的树就是在这个新图中从点1开始的一棵外向树,由于这个新图无环,所以只需要将每个点(除了1)都找一个父结点就行了,结果就是除1外的所有点入度之积。

posted @ 2012-08-31 18:05 Mato_No1 阅读(671) | 评论 (1)编辑 收藏

从今天开始,本沙茶的这个空间重新启用。
———————————————————————————————————————————————————
AHOI2012,耻辱的过去,
AHOI2013,光明的未来,
为了自己的集训队梦想,从今天开始,全力复仇!!!
———————————————————————————————————————————————————
以后这个空间里的文章题目都会以【AHOI2013复仇】为前缀……

posted @ 2012-08-26 09:14 Mato_No1 阅读(452) | 评论 (2)编辑 收藏

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