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;
}

总结

要善于拿部分分。

学会建模。

各种算法的模板要打熟练。

posted @ 2019-08-05 17:21  csxsi  阅读(124)  评论(0编辑  收藏  举报