题意:
给定一个长度为 n n n的序列 a a a,求使得 1 ≤ i < j < k ≤ n 1\leq i
数据范围: 3 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 2 × 1 0 5 3\leq n\leq 2\times 10^5, 1\leq a_i\leq 2\times 10^5 3≤n≤2×105,1≤ai≤2×105
题解:
先计算每个值出现的次数,排序去重
令前缀中任意两个值不同的数构成的方案数为 p p r e ppre ppre
前缀中数的个数 p r e pre pre
这里可以利用到类似背包优化的思想来倒序解决问题
第 i i i种数的个数为cnt[i]
第 i i i种数构成的方案数:cnt[i] × 前i-1种数构成的任意两种数的方案数
所以需要维护前i-1种数构成的任意两种数的方案数
前i种数构成的任意两种数的方案数:cnt[i] × i-1种数的个数
代码:
#include
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N], n;
int cnt[N], g;
ll pre[N], ppre[N];
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) {
int j = i + 1;
while(j <= n && a[j] == a[i]) j += 1;
cnt[++g] = j - i;
i = j - 1;
}
if(g < 3) {
puts("0");
return ;
}
ll res = 0;
pre[2] = cnt[1] + cnt[2];
pre[1] = cnt[1];
ppre[2] = 1ll * cnt[1] * cnt[2];
for(int i = 3; i <= n; ++i) {
res += ppre[i - 1] * cnt[i];
ppre[i] = ppre[i - 1] + cnt[i] * pre[i - 1];
pre[i] = pre[i - 1] + cnt[i];
}
printf("%lld\n", res);
}
int main()
{
int T = 1;
// scanf("%d", &T);
for(int i = 1; i <= T; ++i) solve();
return 0;
}
补充思路:
2022/06/06
思路来源于 Codeforces1598 D
正难则反,考虑选择 3 3 3 个数共有 n × ( n − 1 ) × ( n − 2 ) 6 \frac{n\times (n-1)\times (n-2)}{6} 6n×(n−1)×(n−2) 种,减去存在相同数的方案即可,而这里的难点也在于如何减去存在相同数的方案。
这里可以枚举所有数量大于等于 2 2 2 的数 x x x ,然后枚举第三个数 y y y 。
代码:
#include
using namespace std;
typedef long long ll;
const int N = 200010;
ll a[N], n;
void solve() {
scanf("%lld", &n);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
ll ans = n * (n - 1) * (n - 2) / 6;
for (int i = 1; i <= n; ++i) {
int j = i + 1;
while (j <= n && a[j] == a[i]) j += 1;
ll c = j - i;
if (c >= 2) {
ll temp = c * (c - 1) / 2;
temp *= (n - c);
ans -= temp;
if (c > 2) {
ans -= c * (c - 1) * (c - 2) / 6;
}
}
i = j - 1;
}
printf("%lld\n", ans);
}
int main()
{
int T = 1;
// scanf("%d", &T);
for (int i = 1; i <= T; ++i) {
solve();
}
return 0;
}
题意:
给定一个 n n n点 m m m边的带权无向连通图,现在要求保留 n − 1 n-1 n−1条边, d i d_i di表示从 1 1 1到 i i i的距离,使得最小化 ∑ i = 2 n d i \sum\limits_{i=2}^n d_i i=2∑ndi
数据范围:
1 ≤ n ≤ 2 × 1 0 5 1\leq n\leq 2\times 10^5 1≤n≤2×105
n − 1 ≤ m ≤ 2 × 1 0 5 n-1\leq m\leq 2\times 10^5 n−1≤m≤2×105
1 ≤ c i ≤ 1 0 9 1\leq c_i\leq 10^9 1≤ci≤109
题解:
考虑从 1 1 1到每个点 i i i的最短距离,可以由从起点为 1 1 1开始跑一遍 d i j k s t r a dijkstra dijkstra,就正好可以获得 d i d_i di。
而根据 d i j k s t r a dijkstra dijkstra的更新情况,每个点都会被至少松弛一次,最后被松弛的那次对应一条边,而 1 1 1是起点,不会被其他边松弛,这样 n − 1 n-1 n−1个点正好对应 n − 1 n-1 n−1条边。
代码:
#include
using namespace std;
typedef long long ll;
const int N = 200010;
const int M = N << 1;
int h[N], e[M], ne[M], w[M], idx;
int n, m;
struct Node {
int id;
ll d;
bool operator < (const Node& A) const {
return d > A.d;
}
};
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int pre[N];
ll dist[N];
bool st[N];
priority_queue<Node> q;
void dijkstra(int fir) {
while(!q.empty()) q.pop();
for(int i = 1; i <= n; ++i) {
dist[i] = 1e18;
st[i] = false;
}
dist[fir] = 0;
q.push({fir, dist[fir]});
while(!q.empty()) {
Node t = q.top(); q.pop();
int u = t.id;
if(st[u]) continue ;
st[u] = true;
for(int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if(dist[v] > dist[u] + w[i]) {
dist[v] = dist[u] + w[i];
pre[v] = i;
q.push({v, dist[v]});
}
}
}
}
void solve(int ca) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) h[i] = -1;
idx = 0;
for(int i = 1; i <= m; ++i) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
dijkstra(1);
for(int i = 2; i <= n; ++i) printf("%d%c", pre[i] / 2 + 1, " \n"[i == n]);
}
int main()
{
int T = 1;
// scanf("%d", &T);
for(int i = 1; i <= T; ++i) solve(i);
return 0;
}