小结
挂分小天才~
- 两数相乘转化为两数 (log) 函数相加的操作。
- 二进制分组求多源最短路。
- 注意卡常……
A
给出一个长度为 (n) 的数列,要求选出若干个数,且选出的数不能相邻,求选出数的最大乘积,对 (10^9+9) 取模。
(n,a_ile{10^6})。
看到马上想到用 DP。
可以用 (f_{i,1/0}) 来表示前 (i) 个数中,第 (i) 个数选或不选获得的最大价值。但是其实后面 (0/1) 这一维是不需要的,直接用 (f_{i}) 表示前 (i) 个数获得的最大价值即可。
那么显然转移方程有:
初态为 (f_{0}=0,f_{1}=a_1),目标为 (f_{n})。
但是直接相乘会爆掉 ( ext{long long}),因此要用到一个小技巧:将两数相乘转化为两数的 (log) 函数相加。
这样就可以过掉这道题了。
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int A = 1e6 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 9;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar();
int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
ll f[A];
int n, val[A];
double lgsum[A], lg[A];
int main() {
freopen("jsnk.in", "r", stdin);
freopen("jsnk.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
val[i] = read();
lg[i] = log(val[i]);
}
f[0] = 1;
lgsum[1] = lg[1], f[1] = val[1];
for (int i = 2; i <= n; i++) {
lgsum[i] = lgsum[i - 1], f[i] = f[i - 1];
if (lgsum[i - 2] + lg[i] > lgsum[i]) {
lgsum[i] = lgsum[i - 2] + lg[i];
f[i] = 1ll * f[i - 2] * val[i] % mod;
}
}
cout << f[n] << '
';
return 0;
}
B
有一张 (n) 个点 (m) 条边的无向图,边有边权,给出 (k) 个点,求这 (k) 个点中最短路最短的两点之间的最短路。
多组数据。
(Tle10,n,mle100000,边权le1000)。
本地电脑真慢……被卡了 30 分,传到洛谷上就稳 A……
想喷出题人卡 SPFA,不过我喜欢写 Dijkstra qwq
二进制分组最短路的板子题。
因为这 (k) 个点的编号都是不同的,所以每个点编号的二进制表示也一定不同。枚举 (k) 的二进制的每一位,根据每个关键点的编号这一位是否为 1 分组,将编号这一位为 1 的数的最短路设为 0,然后丢到堆中,之后跑最短路。然后判断编号这一位为 0 的点的最短路,更新答案即可。
/*
多测不清空爆零两行泪
*/
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int A = 1e5 + 11;
const int B = 2e5 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar();
int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
struct edge {
int to, nxt, val;
} e[B << 1];
int n, m, k, pos[A], vis[A], cnt, head[A], dis[A], ans = inf;
struct node {
int x, y;
bool operator < (const node &b) const {
return y > b.y;
}
};
inline void add(int from, int to, int val) {
e[++cnt].to = to;
e[cnt].val = val;
e[cnt].nxt = head[from];
head[from] = cnt;
}
inline void dijk(int bit) {
priority_queue <node> Q;
memset(dis, inf, sizeof(dis));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= k; i++) {
if ((1 << bit) & i) {
dis[pos[i]] = 0;
Q.push((node){ pos[i], 0});
}
}
while (!Q.empty()) {
int x = Q.top().x;
Q.pop();
if (vis[x]) continue;
vis[x] = 1;
for (int i = head[x]; i; i = e[i].nxt) {
int to = e[i].to, val = e[i].val;
if (dis[x] + val < dis[to]) {
dis[to] = dis[x] + val;
if (!vis[to]) Q.push((node) { to, dis[to] });
}
}
}
}
inline void solve() {
memset(head, 0, sizeof(head));
cnt = 0;
n = read(), m = read(), k = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read(), val = read();
add(x, y, val), add(y, x, val);
}
int ans = inf;
for (int i = 1; i <= k; i++) pos[i] = read();
for (int i = 0; (1 << i) <= k; i++) {
dijk(i);
for (int j = 1; j <= k; j++) {
if ((1 << i) & j) continue;
if (dis[pos[j]] < ans) ans = dis[pos[j]];
}
}
printf("%d
", ans == inf ? -1 : ans);
}
int main() {
freopen("muzan.in", "r", stdin);
freopen("muzan.out", "w", stdout);
int T = read();
while (T--) solve();
return 0;
}
C
给定一只由 (0,1,2) 构成的数列,求满足三种权值的数量不多于区间长度一半的区间的个数。
(nle5 imes10^6)。
30pts
记录区间内 0,1,2 的个数,然后判断每个区间是否合法。
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int A = 1e6 + 11;
const int B = 1e5 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar();
int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
char s[B];
int n, sum[B][3], a[B], b[B], c[B];
void sub1() {
for (int i = 1; i <= n; i++) {
sum[i][0] = sum[i - 1][0];
sum[i][1] = sum[i - 1][1];
sum[i][2] = sum[i - 1][2];
if (s[i] == '0') sum[i][0]++;
else if (s[i] == '1') sum[i][1]++;
else if (s[i] == '2') sum[i][2]++;
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = i; j >= 1; j--) {
if (sum[i][0] - sum[j - 1][0] > (i - j + 1) / 2) continue;
if (sum[i][1] - sum[j - 1][1] > (i - j + 1) / 2) continue;
if (sum[i][2] - sum[j - 1][2] > (i - j + 1) / 2) continue;
ans++;
}
}
cout << ans << '
';
}
int main() {
freopen("dokuso.in", "r", stdin);
freopen("dokuso.out", "w", stdout);
n = read();
scanf("%s", s + 1);
return sub1(), 0;
return 0;
}
40pts
以 (0) 的情况为例,(1,2) 同理。记 (sum_i) 为 (a_{1sim{i}}) 中 (0) 的个数。那么一个区间 ((j,i]) 合法当且仅当 (sum_i-sum_jledfrac{i-j}{2}),转化一下就是 (2sum_i-ile2sum_j-j)。
记 (X_i=2sum_i-i),那么对于 (0) 这个限制就是要求对于每一个 (i) 有多少个 (j) 满足 (j<i) 且 (X_jge{X_i})。
对于 (1) 和 (2) 同样求出对应的 (Y_i,Z_i)。那么最后的限制就是对于每一个 (i) 求 (j<i) 且 (X_jge{X_i}land{Y_jge{Y_i}}land{Z_j}ge{Z_i}) 的数量。
经典四维偏序问题,可以用 cdq 套 cdq 解决,码 100+ 行得到十分,真是太棒啦!
60pts
去写 40 分的显然是神仙。
考虑将问题转化,本来要求的是对于每一个 (i),(j<i) 且 (X_jge{X_i}land{Y_jge{Y_i}}land{Z_j}ge{Z_i}) 的个数,但是发现我们可以用总区间数减去不合法的区间数,即 (dfrac{n(n+1)}{2}-j<iland((X_j<{X_i})land({Y_j<{Y_i})}land({Z_j}<{Z_i})))
又发现其实后面这三种是没有交集的,因为不可能有两种数超过区间的一半。
所以 (j<iland((X_j<{X_i})land({Y_j<{Y_i})}land({Z_j}<{Z_i}))) 的区间数量 等于 (|j<iland(X_j<{X_i})|+|j<iland({Y_j<{Y_i})}|+|j<iland({Z_j}<{Z_i})|)。
用树状数组求出来,然后求和即可。
时间复杂度 (O(nlog{n}))。
100pts
根据公式 (X_i=2sum_{i}-i) 可得,(|X_i-X_{i-1}|=1)。因此把树状数组改成暴力就可以过这道题。
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
const int N=5000010;
int a[N],sum[N],n,c[N<<1];
ll ss(int x)
{
for(int i=1;i<=n;++i)
{
if(a[i]==x)sum[i]=1;
else sum[i]=-1;
}
int minn=0;
for(int i=1;i<=n;++i)sum[i]+=sum[i-1],minn=min(minn,sum[i]);
for(int i=0;i<=n;++i)sum[i]-=minn;
int id=0,h=0;
ll res=0;
memset(c,0,sizeof(c));
for(int i=0;i<=n;++i)
{
if(sum[i]>id)h+=c[sum[i-1]];
else h-=c[sum[i]];
res+=h;
id=sum[i];
c[sum[i]]++;
}
return res;
}
int main()
{
freopen("dokuso.in","r",stdin);
freopen("dokuso.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%1d",&a[i]);
ll ans=1ll*n*(n+1)/2;
cout<<ans-ss(0)-ss(1)-ss(2);
return 0;
}