作者: KSkun

Manacher算法原理与实现

Manacher算法原理与实现

概述 Manacher算法是一种用于快速计算字符串中回文串长的算法,复杂度为。下面介绍该算 

[BZOJ3438]小M的作物 题解

[BZOJ3438]小M的作物 题解

题目地址:洛谷:【P1361】小M的作物 – 洛谷、BZOJ:Problem  

[HAOI2010]最长公共子序列 题解

[HAOI2010]最长公共子序列 题解

题目地址:洛谷:【P2516】[HAOI2010]最长公共子序列 – 洛谷、BZOJ:Problem 2423. — [HAOI2010]最长公共子序列

题目描述

字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij = yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。

题意简述

求两个串LCS长度及个数。

输入输出格式

输入格式:
第1行为第1个字符序列,都是大写字母组成,以”.”结束。长度小于5000。
第2行为第2个字符序列,都是大写字母组成,以”.”结束,长度小于5000。

输出格式:
第1行输出上述两个最长公共子序列的长度。
第2行输出所有可能出现的最长公共子序列个数,答案可能很大,只要将答案对100,000,000求余即可。

输入输出样例

输入样例#1:

ABCBDAB.
BACBBD.

输出样例#1:

4
7

题解

注意到本题卡空间,要用到滚动数组优化空间。
这里可以把统计方案数跟DP一起做,LCS依然是那个经典的DP问题,只不过这里把第一维给滚动掉了。至于方案数,检查该状态从哪些其他状态转移而来,加上这些状态的方案数即可。如果s1[i]!=s2[j],且dp[i][j]==dp[i-1][j-1],则要减去dp[i-1][j-1]对应的方案数,因为这个方案数会在dp[i][j-1]和dp[i-1][j]被计算两次,算重了。

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

const int MAXN = 5005, MO = 1e8;

int n, m, dp[2][MAXN], dp2[2][MAXN];
char s1[MAXN], s2[MAXN];

int main() {
    scanf("%s%s", s1 + 1, s2 + 1);
    n = strlen(s1 + 1) - 1; m = strlen(s2 + 1) - 1;
    for(int i = 0; i <= m; i++) dp2[0][i] = 1;
    dp2[1][0] = 1;
    int p = 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            dp[p][j] = std::max(dp[p ^ 1][j], dp[p][j - 1]);
            dp2[p][j] = 0;
            if(s1[i] == s2[j]) {
                dp[p][j] = std::max(dp[p][j], dp[p ^ 1][j - 1] + 1);
            }
            if(s1[i] == s2[j] && dp[p][j] == dp[p ^ 1][j - 1] + 1) {
                dp2[p][j] = (dp2[p][j] + dp2[p ^ 1][j - 1]) % MO;
            }
            if(dp[p][j] == dp[p ^ 1][j]) {
                dp2[p][j] = (dp2[p][j] + dp2[p ^ 1][j]) % MO;
            }
            if(dp[p][j] == dp[p][j - 1]) {
                dp2[p][j] = (dp2[p][j] + dp2[p][j - 1]) % MO;
            }
            if(dp[p][j] == dp[p ^ 1][j - 1]) {
                dp2[p][j] = ((dp2[p][j] - dp2[p ^ 1][j - 1]) % MO + MO) % MO;
            }
        }
        p ^= 1;
    }
    printf("%d\n%d", dp[p ^ 1][m], dp2[p ^ 1][m]);
    return 0;
}
[CQOI2014]危桥 题解

[CQOI2014]危桥 题解

题目地址:洛谷:【P3163】[CQOI2014]危桥 – 洛谷、BZOJ:P 

[CTSC2014]企鹅QQ 题解

[CTSC2014]企鹅QQ 题解

题目地址:洛谷:【P4503】[CTSC2014]企鹅QQ – 洛谷、BZOJ 

[JSOI2010]部落划分 题解

[JSOI2010]部落划分 题解

题目地址:洛谷:【P4047】[JSOI2010]部落划分 – 洛谷、BZOJ:Problem 1821. — [JSOI2010]Group 部落划分 Group

题目描述

聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常发生争斗。只是,这一切都成为谜团了——聪聪根本就不知道部落究竟是如何分布的。
不过好消息是,聪聪得到了一份荒岛的地图。地图上标注了N个野人居住的地点(可以看作是平面上的坐标)。我们知道,同一个部落的野人总是生活在附近。我们把两个部落的距离,定义为部落中距离最近的那两个居住点的距离。聪聪还获得了一个有意义的信息——这些野人总共被分为了K个部落!这真是个好消息。聪聪希望从这些信息里挖掘出所有部落的详细信息。他正在尝试这样一种算法:
对于任意一种部落划分的方法,都能够求出两个部落之间的距离,聪聪希望求出一种部落划分的方法,使靠得最近的两个部落尽可能远离。
例如,下面的左图表示了一个好的划分,而右图则不是。请你编程帮助聪聪解决这个难题。
group

题意简述

给你一些点,要求将点划分成K部分,使得所有部分最近点对距离的最小值最大。

输入输出格式

输入格式:
输入文件第一行包含两个整数N和K(1<=N<=1000,1<K<=N),分别代表了野人居住点的数量和部落的数量。
接下来N行,每行包含两个正整数x,y,描述了一个居住点的坐标(0<=x, y<=10000)。

输出格式:
输出一行,为最优划分时,最近的两个部落的距离,精确到小数点后两位。

输入输出样例

输入样例#1:

4 2
0 0
0 1
1 1
1 0

输出样例#1:

1.00

输入样例#2:

9 3
2 2
2 3
3 2
3 3
3 5
3 6
4 6
6 2
6 3

输出样例#2:

2.00

题解

实际上,如果我们将所有点两两建无向边,就是在求无向图上的最小生成森林,且连通块有K个。既然如此,我们想到了最小生成树用的Kruskal算法,直接套用它,而且我们发现,每建一条边,会使连通块个数减少1,即只需建n-k条边后,求此时的连通块间最小边即可。
复杂度O(n^2)

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cmath>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) 
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 1005;

int n, k;

inline double dis(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
} 

struct Edge {
    int u, v;
    double dis;
} edges[MAXN * MAXN];
int tot;

inline bool cmp(Edge a, Edge b) {
    return a.dis < b.dis;
}

int fa[MAXN];

inline int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

inline double kruskal() {
    int cnt = 0;
    for(int i = 1; i <= tot; i++) {
        int f1 = find(edges[i].u), f2 = find(edges[i].v);
        if(f1 != f2) {
            if(cnt == n - k) return edges[i].dis;
            fa[f1] = f2;
            cnt++;
        }
    }
}

struct Point {
    int x, y;
} pts[MAXN];

int main() {
    n = readint(); k = readint();
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= n; i++) {
        pts[i].x = readint(); pts[i].y = readint();
        for(int j = 1; j < i; j++) {
            edges[++tot] = Edge {i, j, dis(pts[i].x, pts[i].y, pts[j].x, pts[j].y)};
        }
    }
    std::sort(edges + 1, edges + tot + 1, cmp);
    printf("%.2lf", kruskal());
    return 0;
}
[SDOI2009]HH去散步 题解

[SDOI2009]HH去散步 题解

题目地址:洛谷:【P2151】[SDOI2009]HH去散步 – 洛谷、BZO 

[SDOI2014]数数 题解

[SDOI2014]数数 题解

题目地址:洛谷:【P3311】[SDOI2014]数数 – 洛谷、BZOJ:P 

[POI2014]HOT-Hotels 题解

[POI2014]HOT-Hotels 题解

题目地址:洛谷:【P3565】[POI2014]HOT-Hotels – 洛谷、BZOJ:Problem 3522. — [Poi2014]Hotel

题目描述

有一个树形结构的宾馆,n个房间,n-1条无向边,每条边的长度相同,任意两个房间可以相互到达。吉丽要给他的三个妹子各开(一个)房(间)。三个妹子住的房间要互不相同(否则要打起来了),为了让吉丽满意,你需要让三个房间两两距离相同。
有多少种方案能让吉丽满意?

输入输出格式

输入格式:
第一行一个数n。
接下来n-1行,每行两个数x,y,表示x和y之间有一条边相连。

输出格式:
让吉丽满意的方案数。

输入输出样例

输入样例#1:

7
1 2
5 7
2 5
2 3
5 6
4 5

输出样例#1:

5

说明

【样例解释】
{1,3,5},{2,4,6},{2,4,7},{2,6,7},{4,6,7}
【数据范围】
n≤5000

题解

参考资料:题解 P3565 【[POI2014]HOT-Hotels】 – – 洛谷博客
很容易有一个思路就是枚举树根点拉成有根树,然后从根的不同子树中任取三个子树,从这些子树的同深度点集中每个子树任取一个,求这样的方案数之和即可。
问题是,从一堆集合中任取三个,再从这三个集合中任取一个元素这样的方案数总和是一个很难求的东西。接下来是一波神仙分析。
当只有3个集合,元素个数分别是a、b、c时,显然上面说的方案数就是abc。
当集合变为4个,新加入的集合元素个数为d时,我们会发现答案会增加d(ab+bc+ac)。
当集合变为5个,新加入的集合元素个数为e时,我们会发现答案会增加e(ab+bc+ac+ad+bd+cd)。
……
注意上面括号内的部分,实际上每一次会增加上一次元素个数×上一次之前的元素个数和这么多,我们把括号内的这个东西维护成sum2,其实际意义是任选2个集合再分别从两个集合中各任选1个的方案数,而维护sum1为所有集合的元素个数和。每次新加入一个集合就可以往答案中加入new×sum2,sum2加入new×sum1,sum1加入new,这样就很好维护了。
于是我们解决了这个数学模型的问题。接下来的就是对这一棵子树的每一个深度集合都做一遍这个操作即可。
总复杂度为O(n^2 \log n)

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) 
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 5005;

int n;

std::vector<int> gra[MAXN];

LL ans, sum1[MAXN], sum2[MAXN];
int cnt[MAXN], mxdep;

inline void dfs(int u, int fa, int dep) {
    cnt[dep]++; mxdep = std::max(mxdep, dep);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa) continue;
        dfs(v, u, dep + 1);
    }
}

int main() {
    n = readint();
    for(int i = 1, a, b; i < n; i++) {
        a = readint(); b = readint();
        gra[a].push_back(b); gra[b].push_back(a);
    }
    for(int i = 1; i <= n; i++) {
        memset(sum1, 0, sizeof(sum1));
        memset(sum2, 0, sizeof(sum2));
        for(int j = 0; j < gra[i].size(); j++) {
            int v = gra[i][j];
            mxdep = 0; dfs(v, i, 1);
            for(int k = 1; k <= mxdep; k++) {
                ans += cnt[k] * sum2[k];
                sum2[k] += sum1[k] * cnt[k];
                sum1[k] += cnt[k];
                cnt[k] = 0;
            }
        }
    }
    printf("%lld", ans);
    return 0;
}
[SDOI2014]数表 题解

[SDOI2014]数表 题解

题目地址:洛谷:【P3312】[SDOI2014]数表 – 洛谷、BZOJ:P