CSP-J/S2019 做题练习(day4)
A - 同花顺
题解
30分做法
爆搜即可。
60分做法
去重+贪心。
100分做法
去重+贪心后,我们要寻找一段符合条件的最长同花上升子序列 \(L\),\(n-L\) 即为所求的答案。
首先对于相同的花色,用一个队列去维护以每一张牌作结尾时能在序列中的所有牌(即维护首尾指针),然后统计出这些牌的数量,取较大值,答案就是牌的总数与这个值得差值。
枚举同花顺在已有的牌里面的最后一张牌,寻找可能的“第一张牌”,令 \(last\) 为当前牌的最后一张牌时,“可能的第一张牌”。
如果当前牌的数值是 \(a\),\(last\) 这张牌的数值是 \(b\),则显然要满足 \(a-b+1≤n\);
数值都是排好序的,随着“最后一张牌”的递增,“可能的第一张牌”显然是不降的,从前往后 \(O(N)\)进行扫一遍即可得到答案。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#define gI gi
#define itn int
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return f * x;
}
int n, ans, sum, m, ton[100003];
bool vis[100003];
struct Node
{
int hs, sz;
bool operator < (const Node &x) const
{
return hs < x.hs || (hs == x.hs && sz < x.sz);
}
bool operator == (const Node &x) const
{
return (hs == x.hs && sz == x.sz);
}
} a[100003];
inline bool cmp(Node x, Node y)
{
if (x.hs == y.hs) return x.sz < y.sz;
return x.hs < y.hs;
}
inline bool pd()
{
for (int i = 1; i < n; i+=1) if (a[i].hs != a[i + 1].hs || (a[i].sz < a[i + 1].sz - 1)) return false;
return true;
}
int main()
{
File("card");
n = gi(); m = n;
bool fl = true;
int Max = 0;
for (int i = 1; i <= n; i+=1)
{
a[i].hs = gi(), a[i].sz = gi(); if (a[i].sz > n) fl = false; Max = max(Max, a[i].hs);
}
sort(a + 1, a + 1 + n);
if (pd()) {puts("0"); return 0;}
n = unique(a + 1, a + 1 + n) - (a + 1);
int head = 0, tail;
for (int i = 1; i <= n; i+=1)
{
if (i == 1 || a[i].hs != a[i - 1].hs) tail = i;
while (a[i].sz - a[tail].sz + 1 > m) ++tail;
head = max(head, i - tail + 1);
}
printf("%d\n", m - head);
return 0;
}
B - 做实验
题目描述
有一天,你实验室的老板给你布置的这样一个实验。
首先他拿出了两个长度为 \(n\) 的数列 \(a\) 和 \(b\),其中每个 \(a_i\) 以二进制表示一个集合。例如数字 \(5 = (101)_2\) 表示集合 {\(1; 3\)}。第 \(i\) 次实验会准备一个小盒子,里面装着集合 \(a_i\) 所有非空子集的纸条。老板要求你从中摸出一张纸条,如果满足你摸出的纸条是 \(a_i\) 的子集而不是 \(a_{i−b_i}\),\(a_{i−b_{i+1}}\),\(...\),\(a_{i}−1\) 任意一个的子集,那么你就要 \(***\);反之,你就逃过一劫。
令你和老板都没有想到的是,你竟然每次都逃过一劫。在庆幸之余,为了知道这件事发生的概率,你想要算出每次实验有多少纸条能使你 \(***\)
输入格式
第一行一个数字 \(n\)。
接下来 \(n\) 行,每行两个整数,分别表示 \(a_i\) 和 \(b_i\)。
输出格式
\(n\) 行,每行一个数字,表示第 \(i\) 次实验能使你 \(***\) 的纸条数。
样例输入 1
3
7 0
15 1
3 1
样例输出 1
7
8
0
数据范围
对于 \(30 \%\) 的数据,\(n, a_i, b_i ≤ 100\)
对于 \(70 \%\) 的数据,\(n, a_i, b_i ≤ 60000\)
对于 \(100 \%\) 的数据,\(n, a_i, b_i ≤ 10^5\)
保证所有的 \(a_i\) 不重复,\(b_i < i\)
题解
题目大意 :有一个长度为 \(n\) 的数列,其中数列的每项 \(a[i]\)是一个集合,给出 \(n\) 个询问,对于给定两个序列 \(a[i]\)和 \(b[i]\),问对于每个 \(a[i]\),它的子集里面有多少不是\(a[i-b[i]] a[i-b[i]+1]… a[i-1]\) 的子集
这个问题的关键在于两个问题,一是枚举 \(a[i]\)的子集,二是记录对于 \(a[i]\)的每一个子集的状态什么时候在数列上出现过?
即问题的关键:枚举子集、记录前一次出现的位置
枚举依据题意 \(S\) 的子集做法:for( int i=s ; i ; i=(i-1)&s )
- 设 \(f[s]\)为 \(s\) 的这个子集最后一次出现在哪一个 \(a[i]\) 当中
- 为什么是对应最后一次出现 \(s\) 状态的 \(a[ \ ]\)位置?对于序列从前往后处理 \(a[i]\),且询问子集满足不在 \(a[i-b[i]] a[i-b[i]+1]… a[i-1]\) 这些之中。
- 从 \(a[1]\)到 \(a[n]\)依次处理。对 \(a[i]\),枚举其所有的子集 \(s\),若 \(f[s] < i-b[i]\),则答案\(+1\)。同时更新 \(f[s]\)。
不难得出\(AC\)代码
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#define gI gi
#define itn int
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return f * x;
}
int n, m, a, b, ans, sum, pos[100003], tem, tot, cnt;
int main()
{
File("test");
n = gi();
memset(pos, -1, sizeof(pos));
for (itn i = 1; i <= n; i+=1)
{
a = gi(), b = gi();
tem = a; ans = 0;
while (tem)
{
if (pos[tem] < i - b) ++ans;
pos[tem] = i;
tem = (tem - 1) & a;
}
printf("%d\n", ans);
}
return 0;
}
C - 拯救世界
题解
\(tarjan\)缩点后重新建图,然后跑一遍最长路,将起点\(s\)到所有酒吧的最长路取\(\max\)即可。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <queue>
#define gI gi
#define itn int
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return f * x;
}
int n, m, U[500003], V[500003], head[500003], nxt[500003], ver[500003], edge[500003], tot, cnt, sum[500003], ans, W[500003], Top, dgs;
int s, p, q[500003], low[500003], dis[500003], dfn[500003], sy[500003], sta[500003], num, vis[500003];
inline void add(int u, int v)
{
ver[++tot] = v, nxt[tot] = head[u], head[u] = tot;
}
inline void add1(int u, int v, int w)
{
ver[++tot] = v, edge[tot] = w, nxt[tot] = head[u], head[u] = tot;
}
void Tarjan(int u)
{
dfn[u] = low[u] = ++num, vis[u] = 1, sta[++Top] = u;
for (int i = head[u]; i; i = nxt[i])
{
int v = ver[i];
if (!dfn[v])
{
Tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (vis[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])
{
++dgs;
do
{
int y = sta[Top];
sum[dgs] = sum[dgs] + W[y];
vis[y] = 0;
sy[y] = dgs;
} while (sta[Top--] != u);
}
}
inline void SPFA()
{
queue <int> q;
for (int i = 1; i <= dgs; i+=1) dis[i] = 0;
int pp = sy[s];
vis[pp] = 1, dis[pp] = sum[pp], q.push(pp);
while (!q.empty())
{
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = nxt[i])
{
int v = ver[i], w = edge[i];
if (dis[v] < dis[u] + w)
{
dis[v] = dis[u] + w;
if (!vis[v])
{
vis[v] = 1;
q.push(v);
}
}
}
}
}
int main()
{
File("save");
n = gi(), m = gi();
for (int i = 1; i <= m; i+=1)
{
U[i] = gi(), V[i] = gi();
add(U[i], V[i]);
}
for (int i = 1; i <= n; i+=1) W[i] = gi();
s = gi(), p = gi();
for (int i = 1; i <= p; i+=1) q[i] = gi();
for (int i = 1; i <= n; i+=1)
{
if (!dfn[i]) Tarjan(i);
}
memset(head, 0, sizeof(head));
memset(ver, 0, sizeof(ver));
memset(edge, 0, sizeof(edge));
memset(nxt, 0, sizeof(nxt));
tot = 0;
for (int i = 1; i <= m; i+=1)
{
if (sy[U[i]] != sy[V[i]]) add1(sy[U[i]], sy[V[i]], sum[sy[V[i]]]);
}
//memset(vis, 0, sizeof(vis));
SPFA();
ans = 0;
for (itn i = 1; i <= p; i+=1)
{
ans = max(ans, dis[sy[q[i]]]);
}
printf("%d\n", ans);
return 0;
}
总结
要善于拿部分分。
学会建模。
各种算法的模板要打熟练。