最长公共子序列(LCS)
题目描述 给出 1,2,…,n 的两个排列P1和P2,求它们的最长公共子序列。
输入格式 第一行是一个数 n。
接下来两行,每行为 n 个数,为自然数1,2,…,n 的一个排列。
输出格式 一个数,即最长公共子序列的长度。
输入输出样例 输入 5 3 2 1 4 5 1 2 3 4 5 输出 3 说明/提示 对于 50% 的数据, n≤10^3 对于 100% 的数据,n≤10^5
我们可以用
dp[i][j]
来表示第一个串的前ii位,第二个串的前j位的LCS
的长度,那么我们是很容易想到状态转移方程的:如果当前的
A1[i]
和A2[j]
相同(即是有新的公共元素) 那么dp[ i ] [ j ] = max(dp[ i ] [ j ], dp[ i-1 ] [ j-1 ] + 1);
如果不相同,即无法更新公共元素,考虑继承:
dp[ i ] [ j ] = max(
dp[ i-1 ][ j ]
,dp[ i ][ j-1 ]
)这样显然超时
#include<iostream> using namespace std; int dp[1001][1001],a1[2001],a2[2001],n,m; int main() { //dp[i][j]表示两个串从头开始,直到第一个串的第i位 //和第二个串的第j位最多有多少个公共子元素 cin>>n>>m; for(int i=1;i<=n;i++)scanf("%d",&a1[i]); for(int i=1;i<=m;i++)scanf("%d",&a2[i]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); if(a1[i]==a2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1); //因为更新,所以++; } cout<<dp[n][m]; }
就像假如两个数组:1都在3的后面,4都在1的后面,5都在4的后面,那么最长的子序列就是3 1 4 5.只与顺序有关。将第一个数组a 的顺序记录为1 2 3 4 5,然后将后一个数组b的值记录为在a中出现的顺序,(为什么不需要记录下来原值?因为与原值的大小无关,只与顺序有关)。举例如:a 值为3 2 1 4 5,顺序为1 2 3 4 5;b值为1 2 3 4 5,顺序为3 2 1 4 5.这里需要明白顺序小的出现在后面说明与原序列不符,因为序列是有顺序的,所以我们来考虑顺序问题(考虑顺序的问题肯定是与顺序的大小值有关了):那么开始观察顺序的大小,是不是只要是上升的顺序就不会违背,影响第一个数组的顺序,所以我们要在b数组中找到最大上升子序列即可。 ————————————————
(230条消息) Daimayuan Online Judge 最长公共子序列_daimayuan online judge 二分查找_睡不饱的老笨蛋了的博客-CSDN博客
因为最长公共子序列是按位向后比对的,所以a序列每个元素在b序列中的位置如果递增,就说明b中的这个数在a中的这个数整体位置偏后,可以考虑纳入 LCS——那么就可以转变成 nlogn求用来记录新的位置的map数组中的 LIS。 巧妙地将LCS(最长公共子序列)转换成了LIS(最长递增子序列) ————————————————。 原文链接:【题解】【模板】最长公共子序列(LCS)_楚颜a的博客-CSDN博客
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,len,a[N],m[N],b[N],f[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
m[a[i]]=i;
}
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
f[i]=99999999;
}
f[0]=0;
for (int i=1;i<=n;i++)
{
if (m[b[i]]>f[len]) f[++len]=m[b[i]];
else
{
int k=lower_bound(f+1,f+1+len,m[b[i]])-f; //这段代码相当于上面的一串二分查找,就是寻找f[]中第一个大于等于m[b[i]]的数的位置
f[k]=min(m[b[i]],f[k]);
}
}
cout<<len<<endl;
return 0;
}
漂亮数 - 题目 - Daimayuan Online Judge
有一个长度为 n 的数字 X,由 n 位数字组成,即 a1,a2,...an,它们按从左到右的顺序组成一个十进制的数。
并且,你将得到一个数 k,需要你构造出一个最小的数 Y,对于每一位 i(1≤i≤m−k), 满足 bi=bi+k,并且X≤Y。
输入描述 第一行给出两个数 n,k 其中 (2≤n≤200000,1≤k<n)。
第二行给出 X:a1,a2,...an。
输出描述 第一行给出Y的长度 m。
输出最小的满足条件的数 Y:b1,b2,...bm。
输入样例
3 2
353
复制代码
输出样例
3
353
复制代码
这题的意思就是说,用一个长度为k的数来组成一个数y,使得y大于等于x。
为什么是长度为k的数?因为你第i个数和第k+i个数相同,我们就可以把它看作是一个长度为k的轮回,即用一个长度为k的数组成y。
首先先从x处获取前k个数组成y。然后拿y来和x比较(k个数k个数的比),当有个位置上x[i]大于y[i]时,我们给y的最小位+1。因为我们要保证y组成的n个数大于等于x,如果在相同位时x有一个数大于y,那显然是不成立的,所以我们要修改y的值,那么为什么我们要修改的是最低位的值?这里要举个例子说明:
比如x是354365,k是3,那么我们得到的y是354,和x开始k个数k个数的比较,因为我们是从x获取的前k个数,所以下标0k-1这一段是完全相同的。然后我们看后面的365和y比,第二位上6比y的5大,如果我们修改第二位,把它变成6,这样y就变成了364,最后组成的数是364364,而如果我们修改的是最小位的,4变成5,则最后会是355355。显然要比前面那个小。就像我们说的,下标0k-1这一段是完全相同的,第一次比较的最低位对于后面比较的任何一位都是高位数,所以修改了这一位后,后面我们就可以不用管了,毕竟高位上已经比x大了,后面数为何种情况都不会使得x大于y。
说人话就是,我们在比较的过程中,只要有一位是x比y大,那我们直接给y最低位+1就可以了,而且经过这一步操作后就直接结束比较。
那么有没有可能最后组成的数会比x还多一位呢?当然是不可能的,比较x可以等于y,不管怎么样,我们最多把每一位都变成9,那么无论如何都能满足y大于等于x的,没必要多一位。
链接:https://juejin.cn/post/7087541096187691039
#include<iostream>
using namespace std;
const int N = 2e5 + 10;
int n, k;
int a[N],b[N];
int main()
{
cin >> n >> k;
getchar();//读取缓冲区回车键
for (int i = 0; i < n; i++)//注意下标的起始
{
a[i] = getchar() - '0';
if(i<k)
b[i] = a[i];
}
int flag = 0;
for (int i = 0; i <n; i+=k)
{
for (int j = 0; j <k; j++)
{
if (b[j] < a[i+j])
{
int cnt = k-1;
while (b[cnt] == 9) b[cnt]=0, cnt--;
b[cnt]++;
flag = 1;
break;
}
else if (b[j] > a[i+j])
{
flag = 1;
break;
}
else if (b[j] == a[i+j])continue;
}
if (flag)break;
}
cout << n << endl;
for (int i = 0; i < n; i++)
{
cout << b[i % k];
}
return 0;
}
真假字符串
给定两个长度相等的字符串 S1,S2, 问能否找出一个字符串 S, 使得 S 只删除一个字符可以得到 S1, 并且 S 只删除一个字符也可以得到 S2 (可以是不同位置的字符)。
输入格式
输入第一行给出字符串 S1, 第二行给出字符串 S2, 两个字符串的长度 1≤len≤300000。
输出格式
如果能找到满足条件的字符串 S, 输出 1, 否则输出 0。
样例输入
abacaa
aacaba
样例输出
1
样例解释
abacaba 删除第二个字符 b 可以得到字符串 S1, 并且删除第一个字符 b 可以得到字符串 S2。
如果题目说的,有一个字符串s可以通过删除一个字符得到s1和s2,那说明要么s1和s2相等(s删除的地方一样),要么说明s1和s2有一处地方不一样(s删除的地方不一样),即我们可以把题目转化成:s1和s2是否可以最多删除一个字符后相等。
我们先遍历一下两个字符串,找第一个不相同的地方,如果没有,说了s1和s2相同,我们直接输出1。如果有不相同的地方,则要分两种情况讨论了:
1、我们删掉s1的一个字符,然后继续比较s1和s2,如果找到下一处地方不一样,就删除s2的一个字符,经过这次删除后如果两个字符串相同了就是1,不然我们就看第二种情况。
2、我们删掉s2的一个字符,然后继续比较s1和s2,如果找到下一处地方不一样,就删除s1的一个字符,经过这次删除后如果两个字符串相同了就是1,反之是0。
原文链接:https://blog.csdn.net/fnmdpnmsl/article/details/124258745
#include<iostream>
#include<cstring>
using namespace std;
#define endl '\n';
bool cmp(string s1, string s2)//s1是待删除字符串,s2是比较字符串
{
int n = s1.size(), p1 = 0, p2 = 0;
bool st = true;
while (p1 < n && p2 < n)
{
if (s1[p1] != s2[p2])
{
if (st)
{
p1++;//相当于删去s1的当前字符
st = false;
}
else return false;
}
else p1++, p2++;
}
return true;
}
int main()
{
string s1, s2;
cin >> s1 >> s2;
int n = s1.size(), ans = -1;
for (int i = 0; i < n; i++)
{
if (s1[i] != s2[i])
{
ans = i;
break;
}
}
if (ans == -1)
{
cout << 1 << endl;
}
else
{
string str1 = s1, str2 = s2;
str1.erase(ans, 1);
str2.erase(ans, 1);
if (cmp(s2, str1) || cmp(s1, str2))
{
cout << 1 << endl;
}
else cout << 0 << endl;
}
return 0;
}
#812. 互质
题目描述
给你一个包含n个正整数的序列 A=(A1,A2,...,An)A=(A1,A2,...,An),找到 [1,m][1,m]中每一个满足下列条件的 kk:
gcd(Ai,k)=1gcd(Ai,k)=1, 1≤i≤n1≤i≤n
输入描述
第一行输入两个整数 nn, mm 第二行输入n个整数代表序列A
输出描述
第一行输出一个整数代表满足条件的k的数量 接下里每一行输出一个整数,代表一个满足条件的k
样例输入
3 12
6 1 5
样例输出
3
1
7
11
数据范围
1≤n,m≤1000001≤n,m≤100000 1≤ai≤100000
首先,要互质说明不能有一样的因数,那么我们可以把所有的数先分解因数,然后再从1枚举到m,只要能被这些因数整除的就不是合格的k,我们记录下所有合格k的数量然后从小到大输出即可。
最朴素的暴力做法了,我们想办法优化一下它。
实际上我们不用求出所有的因数的,大的因数可以经由小的因数得到,而且如果一个数不能被小因数整除,那么经由小因数得到的大因数肯定也无法整除这个数,比如2无法整除9,那么经由2得到的4 6 8等肯定也无法整除。所有我们不用求所有的因数,而是去求质因数(因为质因数是无法通过小因数得到的,它是质数啊),那么我们就把所有的数分解质因数,然后通过线性筛,筛掉所有不合格的k即可。如果当前数已经是质数了就不用分解质因数直接线性筛即可。
原文链接:https://blog.csdn.net/fnmdpnmsl/article/details/124331034
#include<iostream>
using namespace std;
#include<vector>
#define endl '\n';
typedef long long ll;
const int N = 100500;
bool vis[N], factor[N], is_prime[N];//factor数组记录序列A中数字的所有因数,is_prime数组标记该数是不是质数,是质数值为0,不是值为1
int minp[N];
int main()
{
int n, m, x;
cin >> n >> m;
minp[1] = 1;
for (ll i = 2; i <= N; i++)
{
if (is_prime[i])continue;
for (ll j = i * 2; j <= N; j += i)
{
is_prime[j] = true;
minp[j] = i;
}
minp[i] = i;
}//找每个数大于2的最小因数
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
if (vis[x])continue;//防止重复计算同时标记x这个数为不满足条件的数
vis[x] = true;
while (x != 1)
{
factor[minp[x]] = 1;//找x的所有因数并标记
x /= minp[x];
}
}
vector<int>k;
k.push_back(1);//1一定是满足条件的,与所有数字都互质
for (int i = 2; i <= m; i++)
{
if (factor[i])//2到m范围的数中只要是A中数字因数的倍数,就不满足条件,,用vis数组标记为1
for (int j = i; j <= m; j += i)
vis[j] = 1;
}
for (int i = 2; i <= m; i++)
if (!vis[i])
k.push_back(i);
cout << k.size() << endl;
for (auto i : k)
{
cout << i << endl;
}
return 0;
}
排队
古老的星球上有这样一群人,他们每年都会参加盛大的周年庆。在进入场地之前所有人在入口排成两队,每队人数都是 n 人,第一队第 i 人身高为 ai,第二队第 i 人身高为 bi。
人们在排队时,喜欢跟另一队相同位置的伙伴亲切地交谈。但是如果这两人身高相差太多,他们会感到有些尴尬,两人的尴尬指数 gi 与身高差呈这样一种关系:gi=(ai−bi)2。
为了尽可能减轻尴尬,每队的人都可以与前后相邻者交换位置,次数不限,但只能在同队范围内交换。问最少需要交换多少次位置,可以使得总体尴尬度 ∑(ai−bi)2 最小。如果答案太大,请输出这个最少交换次数对 108−7 取模的结果。
保证每队人的身高两两不同。
输入格式
输入共三行。第一行是一个正整数 n,表示每队的人数。 第二行有 n 个整数,每两个整数用一个空格隔开,表示第一队人们的身高。 第三行有 n 个整数,每两个整数用一个空格隔开,表示第二队人们的身高。
输出格式
一个整数,表示最少的交换次数。
数据范围
1≤n≤105,0≤ai,bi<231,保证每队人们的身高各不相同
输入样例
4
2 3 1 4
3 2 1 4
1
2
3
输出样例
1
1
样例解释
只需交换第一队前两人,或者交换第二队前两人。这样人们都跟与自己身高相同的伙伴交谈,总体尴尬度为0,即最小。
首先,我们要(a-b)2最小,我们把式子展开得到(a2-2*a *b-b2),然后我们应该清楚,不管a和b位置如何,最后的结果a2和b2这两种情况是改不了的,所以我们只能从2ab上下手,然后通过这个式子我们也能看出,要想(a-b)2最小,那么2ab应该要最大。
此时问题初步变成了,如何排序能让2ab最大。这里涉及到一个知识(我觉着挺神奇),就是说如果两个数组相乘,有序的数组相乘会大于无序的数组相乘。(关于证明感兴趣的可以去上网学习一下),所以我们现在要做的就是把一个数组对于另一个数组相对有序:两边数组第一大的在同一个位置,第三大的在同一个位置,第二大的在同一个位置(不是说数组必须有序,而是相对有序)。
现在题目最终变成了,每次可以交换两个相邻的数,问两个数组变的相对有序,步数最少是几步。这一步就相当于是求逆序对了,我们固定一个数组的顺序,仅改变另一个数组的值。那我们就根据固定的数组为准来离散化另一个数组,然后求出这个数组的逆序对数量,至于求逆序对用线段树或者逆序对都是可以的。
原文链接:https://blog.csdn.net/fnmdpnmsl/article/details/124350254
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e8 - 7;
ll f[4 * N];
void revise(int k, int l, int r, int x)
{
if (l == r)
{
f[k] += 1;
return;
}
int m = (l + r) / 2;
if (x <= m)revise(k + k, l, m, x);
else revise(k + k + 1, m + 1, r, x);
f[k] = (f[k + k] + f[k + k + 1])%MOD;
}
ll calc(int k, int l, int r, int x, int y)
{
if (l == x && y == r)
{
return f[k];
}
int m = (l +r) / 2;
if (y <= m)return calc(k + k, l, m, x, y);
else
if (x > m)return calc(k + k + 1, m + 1, r, x, y);
else return (calc(k + k, l, m, x, m) + calc(k + k + 1, m + 1, r, m + 1, y))%MOD;
}
int main() {
int n;
cin >> n;
vector<PII>a(n), b(n);
for (int i = 0; i < n; i++)
{
cin >> a[i].first;
a[i].second = i;
}
for (int i = 0; i < n; i++)
{
cin >> b[i].first;
b[i].second = i;
}
sort(a.begin(), a.end());
sort(b.begin(), b.end());
vector<int>v(n);
for (int i = 0; i < n; i++)
{
v[a[i].second] = b[i].second + 1;
}
ll res = 0;
for (int i = n-1; i >= 0; i--)
{
if (v[i] != 1)res = (res + calc(1, 1, n, 1, v[i] - 1))%MOD;
revise(1, 1, n, v[i]);
}
cout << res << endl;
return 0;
}
最短路计数
题目描述
给出一个 NN 个顶点 MM 条边的无向无权图。
问从顶点 11 开始,到其他每个点的最短路有几条。
输入格式
第 11 行包含两个正整数 NN,MM。
接下来 MM 行,每行两个正整数 x,yx,y 表示存在一条由顶点 xx 到顶点 yy 的边。(可能存在重边和自环)
输出格式
输出 NN 行,每行一个非负整数。
第 ii 行输出从顶点 11 到顶点 ii 的不同最短路个数。
由于数据可能很大,你只需要输出 ansmod100003ansmod100003 的结果。
若顶点 11 不能到达顶点 ii,请输出 00。
样例输入
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
样例输出
1
1
1
2
4
数据范围
1≤N≤1061≤N≤106,1≤M≤2×1061≤M≤2×106。
提示
由于数据量较大,请使用较为快速的输入/输出方式。
我第一眼看见最短路计数其实是一头雾水
但是我们可以再想一想,只要起点到一个点的距离有几种不同走法但是所过路径权值和相等就说明到这个点的最短路种数不同
也就是说,要求A到B的最短路的方案数,也就是说求A到B有多少条路径是最短且总权值相等的
我们如何解决这个问题呢?
根据最短路算法的方式,他是根据前面点最短路的情况来决定后面点的情况的。
那么如果我们要求当前点的方案数,我们就需要分两种情况 第一种是到当前点的最短路需要更新:
那么就要根据松弛操作里的:
if (dis[a[k].to]>dis[v]+a[k].dis) dis[a[k].to]=dis[v]+a[k].dis;
当前的方案数也就是中间点v的方案数
第二种就是当前的到B的最短路不需要更新,即出现
dis[a[k].to]=dis[v]+a[k].dis
由于出现了与之前中间点不同的最短路相同的情况,则到当前B点的方案数加上这个中间点的方案数
注意:第一种情况的改变是通过替换,第二种情况是累加!
其他的都是一样的,根据SPFA的模板套一下即可 感慨:这种根据前驱来决定当前的思想真的很实用也很重要啊!
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#include<utility>
using namespace std;
const int N = 1e6 + 10;
const int M = 4e6 + 10;
const int mod = 100003;
typedef pair <int, int> pi;
inline int read()
{
char ch = getchar();
int x = 0, f = 1;
while ((ch > '9' || ch < '0') && ch != '-')
ch = getchar();
if (ch == '-')
{
f = -1;
ch = getchar();
}
while ('0' <= ch && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int e[M], h[N], ne[M], idx, n, m, dist[N], ans[N],vis[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra()
{
priority_queue<pi, vector<pi>, greater<pi>>q;
q.push({ 0,1 });
while (q.size())
{
int t = q.top().second;
q.pop();
if (vis[t])continue;
vis[t] = 1;
for (int i = h[t]; ~i; i = ne[i])
{
int to = e[i];
if (dist[to] > dist[t] + 1)
{
dist[to] = dist[t] + 1;
ans[to] = ans[t];
q.push({ dist[to],to });
}
else if (dist[to] == dist[t] + 1)
{
ans[to] += ans[t];
ans[to] %= mod;
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
memset(dist, 0x3f, sizeof dist);
n = read(), m = read();
for (int i = 1; i <= m; i++)
{
int a, b;
a = read(), b = read();
add(a, b), add(b, a);
}
dist[1] = 0, ans[1] = 1;
dijkstra();
for (int i = 1; i <= n; i++)
{
printf("%d\n", ans[i]);
}
return 0;
}
最后的舞会
老师为即将毕业的同学们准备了一场舞会,有2N2N个同学会参加这场舞会,他们会被分成NN对跳舞,每个人有一个编号,如果编号为ii的同学和编号为jj的同学配对,那么这一对的满意度是Ai,j(i<j)Ai,j(i<j),我们规定这个舞会的满意度为每一队的满意度的异或和,也就是说,当同学们分成NN组后,第ii对同学的满意度为AiAi,那么舞会的满意度为A1⊕A2⊕...ANA1⊕A2⊕...AN
请你求出本场舞会满意度的最大值
输入描述
第一行给出一个数NN,有2N2N个人参加舞会
接下来给出一个矩阵表示ii和jj配对时的满意度
A1,2,A1,3,...A1,2NA1,2,A1,3,...A1,2N
A2,3,...A2,2NA2,3,...A2,2N
.. .. ..
A2N−1,2NA2N−1,2N
其中1≤N≤8,0≤Ai,j≤2301≤N≤8,0≤Ai,j≤230
输出描述
输出本场舞会满意度的最大值
样例输入
2 4 0 1 5 3 2
样例输出
6
样例解释
如果{1,2},{3,4},ans=A1,2⊕A3,4=4⊕2=6{1,2},{3,4},ans=A1,2⊕A3,4=4⊕2=6
如果{1,3},{2,4},ans=A1,3⊕A2,4=0⊕3=3{1,3},{2,4},ans=A1,3⊕A2,4=0⊕3=3
如果{1,4},{2,3},ans=A1,4⊕A2,3=1⊕5=4{1,4},{2,3},ans=A1,4⊕A2,3=1⊕5=4
最后答案为max(6,3,4)=6max(6,3,4)=6
中文
可以看到n的范围很小,由n的范围可以很容易想到用dfs搜索,个人认为此题与八皇后问题有些类似,也是考虑每一行每一列都只能选一个数。考虑搜索每一对,一对一对去匹配,而此题如果爆搜容易超时所以需要进行剪枝。剪枝的基本思想对于此题是减少重复,而因为此题每个数都必须被选到,所以可以在搜索由i递增依次去搜。对1可以直接加入,因为1肯定会被匹配,再去找与1匹配的位置(小剪枝)。因为每一个数都必须被匹配到,所以在对每一对的匹配搜索中,找到第一个未被匹配到的数就可以对其进行匹配,进而枚举与它匹配的位置。
这里还有一个小的注意点,因为aij的范围<=2^30,不会爆int,可以直接用int就行,实测开longlong比int慢将近两倍多
代码:
#include <bits/stdc++.h>
using namespace std;
bool vis[20];
int a[20][20];
int ans, n;
void dfs(int step, int res) // step为当前已经匹配的对数,res是当前的异或和
{
if (step == n) //已全部匹配更新答案
{
ans = max(ans, res);
return;
}
int i;
for (i = 2; i <= n + n - 1; i++)
{
if (!vis[i]) //找到1~2n中第一个未被匹配的数(剪枝)
break;
}
for (int j = i + 1; j <= n + n; j++)
{
if (vis[j] == 0) //未被匹配
{
vis[i] = 1, vis[j] = 1; //匹配
dfs(step + 1, res ^ a[i][j]);
vis[i] = 0, vis[j] = 0; //取消标记
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n + n - 1; i++)
for (int j = i + 1; j <= n + n; j++)
cin >> a[i][j];
vis[1] = 1; // 1肯定会被匹配,直接加入匹配(剪枝)
for (int i = 2; i <= n + n; i++) //枚举与1匹配的位置
{
vis[i] = 1;
dfs(1, a[1][i]);
vis[i] = 0;
}
cout << ans;
}