2020.11.13 “考试”

小结

挂分小天才~

  • 两数相乘转化为两数 (log) 函数相加的操作。
  • 二进制分组求多源最短路。
  • 注意卡常……

A

给出一个长度为 (n) 的数列,要求选出若干个数,且选出的数不能相邻,求选出数的最大乘积,对 (10^9+9) 取模。
(n,a_ile{10^6})

看到马上想到用 DP。
可以用 (f_{i,1/0}) 来表示前 (i) 个数中,第 (i) 个数选或不选获得的最大价值。但是其实后面 (0/1) 这一维是不需要的,直接用 (f_{i}) 表示前 (i) 个数获得的最大价值即可。

那么显然转移方程有:

[f_{i}=max(f_{i-1},f_{i-2} imes{a_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;
}
原文地址:https://www.cnblogs.com/loceaner/p/13971516.html