2021年8月26日模拟赛题解

T1 偶数个 3\text{T1 偶数个 3}

题目大意

编程求出所有的 nn 位数中,有多少个数中有偶数个数字 33

0<n<10000 < n < 1000

说明:

  • 一位数有 99 个:1234567891、2、3、4、5、6、7、8、9
  • 00 不是一位数。
  • 00 为什么不是一位数?原因参考于《九年义务教育六年制小学数学第八册教师教学用书》。
  • 包含偶数个 33 可以为包含 0033

解题思路

一道显然的数论题。

首先设 fif_iii 位数中有奇数个数字 33 的数的个数,设 aia_iii 位数中有偶数个数字 33 的数的个数。

显然我们可以从前一个状态推出当前的状态。

即在前一个状态的后面加上一个数。

即如果在原来有奇数个 33 的数后加上 33 就是当前的偶数个数字 33 的数,方案数为 fi1f_{i-1}

如果在原来有偶数个 33 的数后加上 33 就是当前的奇数个数字 33 的数,方案数为 ai1a_{i-1}

如果在原来有奇数个 33 的数后加上不为 33的数字就是当前的奇数个数字 33 的数,方案数为 9fi19*f_{i-1}

如果在原来有偶数个 33 的数后加上不为 33的数字就是当前的偶数个数字 33 的数,方案数为 9ai19*a_{i-1}

则有转移方程:

fi=9fi1+ai1f_i=9*f_{i-1}+a_{i-1}

ai=9ai1+fi1a_i=9*a_{i-1}+f_{i-1}

AC CODE

#include <bits/stdc++.h>
#define int long long
using namespace std;

long long n;

long long f[100005], a[100005];

long long mod = 12345;

signed main()
{
	scanf("%lld", &n);
	f[1] = 1;
	a[1] = 8;
	f[2] = 17;
	a[2] = 73;
	for(int i = 3; i <= n; ++i)
	{
		f[i] = f[i - 1] % mod * 9 % mod + a[i - 1] % mod;
		f[i] %= mod;
		a[i] = a[i - 1] % mod * 9 % mod + f[i - 1] % mod;
		a[i] %= mod;
	}
	printf("%lld", a[n] % 12345);
	return 0;
}

T2 购物\text{T2 购物}

题目大意

nn 件商品,每一件商品价格为 PiP_i 个单位。

现在你手中共有 mm 个单位的现金,以及 kk 张优惠券。

你可以在购买某件商品时,使用至多一张优惠券,若如此做,该商品的价格会下降至 QiQ_i

请问你至多能购买多少件商品。

解题思路

考虑贪心,先不考虑有没有优惠卷的情况肯定是从小到大买,这样一定会买的最多的。

按照这个思想,每一次买的时候就找最小的。

有两种情况:

  • 使用了优惠券之后获得了一个最小值。
  • 没有使用优惠券的最小值。

分别对 ppqq 进行排序。

每次找最小的。

还要将用来优惠券的那些东西按照 piqip_i − q_i 放到堆里面,为以后撤销优惠券使用。

这就是反悔贪心。

AC CODE

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
#define int long long
 
const int maxn = 1e6 + 5;
 
struct node
{
    int p, q;
    bool operator < (const node& rhs) const
    {
        return p - q > rhs.p - rhs.q;
    }
} a[maxn];
 
int n, k, ans;
long long m;
priority_queue<node> Q;
 
bool cmp(node a, node b)
{
    if(a.q != b.q)
        return a.q < b.q;
    return a.p < b.p;
}
 
bool CMP(node a, node b)
{
    return a.p < b.p;
}
 
signed main()
{
    scanf("%lld%lld%lld", &n, &k, &m);
    for (int i = 1; i <= n; i++)
        scanf("%lld%lld", &a[i].p, &a[i].q);
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= k; i++)
    {
        m -= a[i].q;
        Q.push(a[i]);
        if (m < 0)
        {
            printf("%lld\n", i - 1);
            return 0;
        }
    }
    ans = k;
    sort(a + k + 1, a + n + 1, CMP);
    for (int i = k + 1; i <= n; i++)
    {
        int tp = Q.top().p, tq = Q.top().q;
        if (a[i].p - a[i].q > tp - tq && a[i].q + tp - tq <= m)
        {
            m -= (a[i].q + tp - tq);
            ans++, Q.pop(), Q.push(a[i]);
        }
        else if (a[i].p <= m)
            ans++, m -= a[i].p;
    }
    printf("%lld\n", ans);
    return 0;
}

T3 拆网线\text{T3 拆网线}

题目大意

给出一有 nn 个点的树,现在要拆除一些线,但是需要保证至少有 kk 个点,满足每个点都可以和至少一个点联通。

解题思路

做法一

考虑树形 DP

显然,对于两点一线的情况的个数,应该越多越好。

假设如果是 xx 对点(两点一线),且 x2kx*2≥k,那么只需要 (k+1)/2(k+1)/2 条边。

否则,说明有多出的点,多出的点要连出一条边与两点一线联通,则需要 x+(kx2)x + (k-x*2) 条边。

现在问题就转为求这样的点对有多少。

dpi,1dp_{i,1} 表示以 ii 为根的子树中能够组成许多两点一线的最大点数,包含节点 ii

dpi,0dp_{i,0}vsons[u]dpv,1\sum\limits_{v \in sons[u] }^{} dp_{v,1}

当结点 uu 与它的子结点 vv 连边时,其余子结点都无法与结点 uu 连边,并且结点 vv 的子结点无法和结点 vv 连边,所以 dpu,1=max(dpu,1,dpu,0dpv,1+dpv,0+1)dp_{u,1}=max(dp_{u,1},dp_{u,0}-dp_{v,1}+dp_{v,0}+1)

转移方程:

dpu,0=vsons[u]dpv,1dp_{u,0}=\sum\limits_{v \in sons[u] }^{} dp_{v,1}

dpu,1=max(dpu,1,dpu,0dpv,1+dpv,0+1)dp_{u,1}=max(dp_{u,1},dp_{u,0}-dp_{v,1}+dp_{v,0}+1)

最后 dp1,1dp_{1,1} 就是上面所说的 xx 了。

做法二

当然,有 DP 就有贪心,先思考这张图。

在这里插入图片描述

对于这张图,思考,我们要从上往下匹配,还是从下往上匹配。

显然是从下往上匹配,因为一个子节点往上只有一个父节点,但一个节点往下可能有多个子节点。

显然最优的情况一定是一条边匹配两个点,即两点一线。

然后从下往上匹配,每遇到两个没访问过的点,就相连,这就是贪心的思路。

但是有可能树的形状不能满足这样的匹配,于是剩下的点就只能用一条边匹配了。

AC CODE

树形 DP

#include <bits/stdc++.h>
using namespace std;

#define _ 100005

int T, n, k, ans;

int tot, head[_], to[_ << 1], nxt[_ << 1];

int f[_][2];

void add(int u, int v)
{
    to[++tot] = v;
    nxt[tot] = head[u];
    head[u] = tot;
}

void dfs(int u, int fa)
{
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == fa) continue;
        dfs(v, u);
        f[u][0] += f[v][1];
    }
    f[u][1] = f[u][0];
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == fa) continue;
        f[u][1] = max(f[u][1], f[u][0] - f[v][1] + f[v][0] + 1);
    }
}

signed main()
{
    scanf("%d", &T);
    while(T--)
    {
    	tot = 0;
    	memset(head, 0, sizeof head);
    	memset(f, 0, sizeof f);
        scanf("%d%d", &n, &k);
        for(int i = 1; i < n; ++i)
        {
            int a;
            scanf("%d", &a);
            add(i + 1, a);
            add(a, i + 1);
        }
        dfs(1, 0);
        int kkk = (k & 1);
        k = k - kkk;
        if(f[1][1] * 2 >= k) printf("%d\n", k / 2 + kkk);
        else printf("%d\n", f[1][1] + (k - f[1][1] * 2) + kkk);
    }
    return 0;
}

贪心

#include<bits/stdc++.h>
using namespace std;
const int _ = 100005;

int tot, ans;

int head[_], nxt[_ << 1], to[_ << 1];

bool vis[_];

inline int read()
{
	int X = 0, w = 1;
	char ch = 0;
	while(ch < '0' || ch > '9')
	{
		if(ch == '-') w = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') X = (X << 3) + (X << 1) + ch - '0', ch = getchar();
	return X * w;
}

inline void add(int x, int y)
{
	nxt[++tot] = head[x];
	head[x] = tot;
	to[tot] = y;
}

inline void dfs(int x, int y)
{
	for(int i = head[x]; i; i = nxt[i])
		if(to[i] != y)
		{
			dfs(to[i], x);
			if(!vis[x] && !vis[to[i]]) vis[x] = vis[to[i]] = 1, ans++;
		}
}

signed main()
{
	int T = read();
	while(T--)
	{
		int n = read(), k = read();
		tot = 0;
		ans = 0;
		memset(head, 0, sizeof(head));
		memset(vis, 0,sizeof(vis));
		for(int i = 1; i < n; i++)
		{
			int a = read();
			add(a, i + 1);
			add(i + 1, a);
		}
		dfs(1, 0);
		if(ans * 2 >= k)
			printf("%d\n",(k + 1) / 2);
		else
			printf("%d\n", ans + (k - ans * 2));
	}
	return 0;
}

T4 密室\text{T4 密室}

题目大意

有一张地图,其中有 NN 个点,且有 MM 个传送门,可以从 xx 点传送到 yy 点(单向边),但通过某个传送门需要一些钥匙。每个房间也有一些钥匙。幸运的是,你用钥匙传送后,钥匙不会消失。

注意,你要先通过传送门,才能拿到这个传送门的终点的钥匙。

假设你身处 11 点,请你计算到处于 nn 点的出口需要经过的最少的传送门数量。

如果你逃不出一个地图,请输出 "No Solution"。

解题思路

首先,因为通过每个传送门的代价都相同,为 11,所以不需要求最短路径,用普通的 BFS 就行了。

首先可以用一个变量 xx 记录所有钥匙的状态,因为你是否拥有一种钥匙的状态,是 1100

即如果你有种类 22 和种类 55 的钥匙。

xx22+25=4+16=202^2+2^5=4+16=20

即现在可以用一个变量表示你拥有的所有钥匙的状态,也可以表示某个传送门需要某些钥匙的状态,总而言之,这种用一个变量表示原先用一个数组表示的东西,叫状态压缩。

然后跑一边 BFS,可以通过一个传送门的条件是 opt&w==woptopt 为你当前拥有的钥匙的状态,ww 为通过当前传送门所需的钥匙的状态,&\& 的定义是有零为假否则为真,刚好符合判断所需的性质)。

如不明白,看这个例子。

你当前所拥有的的钥匙为 \to 0 1 0 1 0 1

若通过传送门所需的钥匙为 \to 1 1 0 1 0 1

两者相 &\&0 1 0 1 0 1,和 1 1 0 1 0 1 不相等,故不能通过。

若通过传送门所需的钥匙为 \to 0 0 0 1 0 1

两者相 &\&0 0 0 1 0 1,和 0 0 0 1 0 1 相等,故能通过。

注意在 BFS 时,visvis 数组要用二维,分别是当前的点和当前拥有的钥匙的状态。

说一个常识(然而我却不知道),BFS 一到终点是必然是最优的。

AC CODE

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 5;

int n, m, tot, head[maxn], x;

int vis[maxn][1025];

int ans = 1e9;

int f[maxn];

struct edge
{
	int to, nxt, w;
} e[maxn];

void add(int a, int b, int w)
{
	e[++tot].to = b;
	e[tot].nxt = head[a];
	e[tot].w = w;
	head[a] = tot;
}

struct abc
{
	int a, b, c;
};

void Bfs()
{
	
	queue<abc> q;

	q.push({1, f[1], 0});

	while (!q.empty())
	{
		abc ABC = q.front();
		int tmp = ABC.a, opt = ABC.b, dis = ABC.c;
		q.pop();
		if(vis[tmp][dis]) continue;
		vis[tmp][dis] = 1;
		if(tmp == n)
		{
			printf("%d", dis);
			exit(0);
		}
		for (int i = head[tmp]; i; i = e[i].nxt)
		{
			if ((e[i].w & opt) == e[i].w)
			{
				q.push({e[i].to, f[e[i].to] | opt, dis + 1});
			}
		}
	}
	printf("No Solution");
}

signed main()
{
	scanf("%d%d%d", &n, &m, &x);
	for(int i = 1; i <= n; ++i)
	{
		for(int j = 0; j < x; ++j)
		{
			int a;
			scanf("%d", &a);
			if(a)
				f[i] += (1 << j);
		}
	}
	for (int i = 1; i <= m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		int l = 0;
		for(int j = 0; j < x; ++j)
		{
			int c;
			scanf("%d", &c);
			if(c)
				l += (1 << j);
		}
		add(a, b, l);
	}
	Bfs();
	return 0;
}
posted @ 2021-08-26 22:08  蒟蒻orz  阅读(3)  评论(0编辑  收藏  举报  来源