题目描述
难度分:1400
输入T(≤2×104)表示T组数据。所有数据的n之和≤105。
每组数据输入n(2≤n≤105),m(0≤m≤105)和m对数字(xi,yi),范围 [1,n]且xi≠yi。
有n个人,编号从1到n:
- 输入的m对数字表示xi和yi互相不认识。
- 不在这m对数字中的任何数对,都互相是朋友关系。
注:从图论角度来说,就是一个m条边的无向图的补图上的边,都表示朋友关系。
问:序列[1,2,…,n]有多少个非空连续子数组b,满足b中的人两两都是朋友?
注意长度为1的b一定符合要求。
输入样例
2
3 2
1 3
2 3
4 2
1 2
2 3
输出样例
4
5
算法
动态规划
先将所有的(xi,yi)放入到一个数组tup中(为了方便,读入数据的时候保证xi<yi成立,不成立就交换xi和yi),然后双关键字排序。接下来遍历tup构建一个映射most_right“i→ i的右边最远的朋友j(满足区间(i,j]内的所有人都是i的朋友)”,这个很好处理,倒序遍历tup时,维护most_right[tup[i][0]]=tup[i][1]−1的最小值即可,这本质是一个递推的DP
。不在映射most_right内的i,就满足most_right[i]=i。
状态定义
dp[i]表示以i开头的,满足内部所有人都互相为好友的子数组有多少个。其实我们只要找到i的j=most_right[i]即可,因为子数组[i,k],k∈[i,j]一定都满足条件,所以一共有j−i+1个以i开头的子数组满足条件。
状态转移
根据状态定义,状态转移方程为dp[i]=most_right[i]−i+1,最终答案就应该是Σni=1dp[i]。
复杂度分析
时间复杂度
排序的时间复杂度为O(nlog2n),之后倒序遍历n到1计算答案时间复杂度为O(n)。因此,整个算法的时间复杂度为O(nlog2n)。
空间复杂度
哈希表most_right的空间复杂度为O(n),有O(n)个键值对。(xi,yi)点对有O(n)个,即tup数组也是O(n)规模的。因此,算法的额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>
using namespace std;
typedef pair<int, int> PII;
int T, n, m, x, y;
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
vector<PII> tup;
for(int i = 1; i <= m; i++) {
scanf("%d%d", &x, &y);
if(x > y) swap(x, y);
tup.push_back({x, y});
}
sort(tup.begin(), tup.end());
unordered_map<int, int> most_right;
for(int i = tup.size() - 1; i >= 0; i--) {
auto& pir = tup[i];
most_right[pir.first] = pir.second - 1;
}
for(int i = 1; i <= n; i++) {
if(most_right.find(i) == most_right.end()) {
most_right[i] = n;
}
}
long long ans = 1;
int mn = most_right[n];
for(int i = n - 1; i >= 1; i--) {
mn = min(mn, most_right[i]);
ans += mn - i + 1;
}
printf("%lld\n", ans);
}
return 0;
}