2021/1/20训练总结

前言

本次训练的内容包含3道数位dp和3到图论题。数位dp都比较常规,如果熟悉板子可以很快写出来。图论题是3道模板题,分别是求割点,求割边,2-SAT问题。三道模板题核心都是tarjan算法,如果不熟悉可以去看一下。

A - Round Numbers

题目链接: POJ - 3252
题目大意:给定 [ L , R ] [L,R] [L,R]求区间内满足二进制数中0个数大于等于1个数的数字个数。
数据范围: 1 ≤ L < R ≤ 2 ∗ 1 0 9 1 ≤ L < R ≤ 2*10^9 1L<R2109
题解:数位 d p dp dp我的写法就是记忆化搜索,如果看过之前题解的可以注意到我的写法几乎没什么变化,只有 d p dp dp数组和dfs函数有不同的区别。这题要注意如果有前置0,那么0的数量不能直接加一。其他的可以看代码
AC代码:

#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<time.h>
#include<stdio.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[50], dp[50][50][50][2];
int dfs(int cur, int limit, int num1,int num0, bool _0)
{
	if (!cur)
	{
		return num1 <= num0;
	}
	if (!limit && ~dp[cur][num1][num0][_0])return dp[cur][num1][num0][_0];
	int ans = 0;
	int tp = limit ? w[cur] : 1;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), num1 + (i == 1), _0 ? 0 : num0 + (i == 0), _0 && (i == 0));
	}
	if (!limit)
		dp[cur][num1][num0][_0] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x &1, x /= 2;
	return dfs(w[0], 1, 0, 0, 1);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	ll l, r; read(l), read(r);
	printf("%lld\n", find(r) - find(l - 1));
	return 0;
}

B - Balanced Number

题目链接: HDU - 3709
题目大意:一个数如果可以以某一个数位作为支点,使得左右力矩相等,则这个数是平衡的。求给定 [ L , R ] [L,R] [L,R]中平衡数的个数。
数据范围: 0 ≤ x ≤ y ≤ 1 0 18 , 0 < T ≤ 30 0 ≤ x ≤ y ≤ 10^{18},0 < T ≤ 30 0xy1018,0<T30
题解:蛮有思维的数位dp,首先我们要想到枚举支点所在的位数。这里先给出一个容易想到搜索写法。

int w[50], dp[20][1600][1600];
int dfs(int cur, int limit, int left,int right,int ph)
{
	if (!cur)
	{
		return left==right;
	}
	if (!limit && ~dp[cur][left][right])return dp[cur][left][right];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp),  cur<=ph?left+i*(ph-cur):left, cur <= ph ? right : right + i * (cur - ph), ph);
	}
	if (!limit)
		dp[cur][left][right] = ans;
	return ans;
}
int find(int x)
{
	if (x <0)return 0;
	if (x == 0)return 1;
	w[0] = 0;
	while (x)w[++w[0]] = x %10, x /= 10;
	int ans = 0;
	for (int i = 1; i <= w[0]; i++)
	{
		memset(dp, -1, sizeof(dp));
		ans += dfs(w[0], 1, 0, 0, i);
	}
	return ans-w[0]+1;//0被多计数了,每次都计算了
}

d f s dfs dfs函数中, c u r cur cur表示剩余的位数, l i m i t limit limit表示是否顶着上界, l e f t left left表示左边力矩大小, r i g h t right right表示右边力矩大小, p h ph ph表示支点所在位。这样我们只要最后判断 l e f t = = r i g h t left==right left==right就行。不过我们发现这样写数组过大!而且每次都要 m e m s e t ( d p , − 1 , s i z e o f ( d p ) ) memset(dp, -1, sizeof(dp)) memset(dp,1,sizeof(dp)) T L E TLE TLE。我们考虑将 l e f t left left r i g h t right right改成 s u m = r i g h t − l e f t sum=right-left sum=rightleft,这样数组就可以少一维,这样就完全ok了
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[50], dp[20][1600][20];
int dfs(int cur, int limit, int sum,int ph)
{
	if (!cur)
	{
		return sum==0;
	}
	if (!limit && ~dp[cur][sum][ph])return dp[cur][sum][ph];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), sum + (cur - ph)*i,ph);
	}
	if (!limit)
		dp[cur][sum][ph] = ans;
	return ans;
}
int find(int x)
{
	if (x <0)return 0;
	
	w[0] = 0;
	while (x)w[++w[0]] = x %10, x /= 10;
	int ans = 0;
	for (int i = 1; i <= w[0]; i++)
	{
		ans += dfs(w[0], 1, 0, i);
	}
	return ans - w[0] + 1;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	int t; read(t);
	while (t--)
	{
		ll l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}
	
	return 0;
}

C - B-number

题目链接:HDU - 3652
题目大意:求 [ 1 , n ] [1,n] [1,n]中满足包含13且可以整除13的数字个数。
数据范围: 1 < = n < = 1 0 9 1 <= n <= 10^9 1<=n<=109
题解:比较经典的数位dp模型了,直接用 n u m num num表示数字模13的值, p r e pre pre表示上一次的数字, 1 3 _13 13表示是否已经包含了13了。然后我们对应转移即可。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[20], dp[15][15][11][2];
int dfs(int cur, int limit, int num,int pre, bool _13)
{
	if (!cur)
	{
		return _13 && (num % 13 == 0);
	}
	if (!limit && ~dp[cur][num][pre][_13])return dp[cur][num][pre][_13];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
		ans += dfs(cur - 1, limit && i == tp, (num * 10 + i) % 13, i, _13 || (pre == 1 && i == 3));
	if (!limit)
		dp[cur][num][pre][_13] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 10, 0);
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	int n; memset(dp, -1, sizeof(dp));
	while (scanf("%d", &n) != EOF)
	{
		printf("%d\n", find(n));
	}
	return 0;
}

D - Forming the Council

题目链接:LightOJ - 1251
题目大意:2-SAT模板题,前置知识是 T a r j a n Tarjan Tarjan求强连通变量
题解:参考P4782 【模板】2-SAT 问题,洛谷的模板题,可以先去题解区学会 2 − S A T 2-SAT 2SAT,然后写一下这题。这题P4171 [JSOI2010]满汉全席也可以写下,最好可以手撸出来,模板思维难度不是很大,主要建边完缩点就出来了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int n, m;
vector<int>p[N];
int tem[N], dfn[N], lown[N], vis[N];
stack<int>stk;
void dfs(int u, int fa)
{
	stk.push(u); vis[u] = 1;
	dfn[u] = lown[u] = ++dfn[0];
	for (auto to : p[u])
	{
		
		if (!dfn[to])
		{
			dfs(to, u);
			lown[u] = min(lown[u], lown[to]);
		}
		else if (vis[to])
			lown[u] = min(lown[u], dfn[to]);
	}
	if (dfn[u] == lown[u])
	{
		tem[0]++;
		while (stk.top() != u)
		{
			tem[stk.top()] = tem[0];
			vis[stk.top()] = 0;
			stk.pop();
		}
		tem[stk.top()] = tem[0];
		vis[stk.top()] = 0;
		stk.pop();
	}

}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	int t; read(t); int qiu = 0;
	while (t--)
	{
		read(m), read(n);
		while (stk.size())stk.pop();
		for (int i = 0; i <= 4 * n; i++)
		{
			p[i].clear();
			dfn[i] = lown[i] = vis[i] = tem[i] = 0;
		}
		for (int i = 1; i <= m; i++)
		{
			int x, y, a = 0, b = 0; char s1, s2;
			scanf(" %c%d %c%d", &s1, &x, &s2, &y);
			if (s1 == '+')a++;
			if (s2 == '+')b++;
			p[x + n * a].push_back(y + n * (b ^ 1));
			p[y + n * b].push_back(x + n * (a ^ 1));
		}
		for (int i = 1; i <= 2 * n; i++)if (!dfn[i])dfs(i, -1);
		bool isok = 1;
		for (int i = 1; i <= n; i++)
		{
			if (tem[i] == tem[i + n])
			{
				isok = 0;
				break;
			}
		}
		if (!isok)printf("Case %d: No\n", ++qiu);
		else
		{
			printf("Case %d: Yes\n", ++qiu);
			vector<int>ans;
			for (int i = 1; i <= n; i++)
				if (tem[i] < tem[i + n])ans.push_back(i);
			printf("%d", ans.size());
			for (auto it : ans)printf(" %d", it);
			printf("\n");
		}
	}
	return 0;
}

E - Critical Links

题目链接: LightOJ - 1026
题目大意:求桥,并输出桥两边端点,
题解:参考P3388 【模板】割点(割顶),学习一下 T a r j a n Tarjan Tarjan算法之后应该可以很快写出来了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, n, m,dfn[N],lown[N],tot;
vector<int>p[N];
vector<pair<int, int>>ans;
void dfs(int u,int f)
{
	dfn[u] = lown[u] = ++tot;
	for (auto to : p[u])
	{
		if (to == f)continue;
		if (!dfn[to])dfs(to,u),lown[u]=min(lown[u],lown[to]);
		lown[u] = min(lown[u], dfn[to]);
		if (lown[to] > dfn[u])ans.push_back({ min(u,to),max(u,to) });
	}
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t); int qiu = 0;
	while (t--)
	{
		read(n);
		for (int i = 0; i <= n; i++)p[i].clear(),dfn[i]=0,lown[i]=0;
		ans.clear(); tot = 0;
		for (int i = 1; i <= n; i++)
		{
			int x, k;
			scanf("%d (%d)", &x, &k);
			for (int j = 1; j <= k; j++)
			{
				int y; scanf("%d", &y);
				p[x].push_back(y);
			}
		}
		for (int i = 0; i < n; i++)if (!dfn[i])dfs(i,-1);
		printf("Case %d:\n", ++qiu);
		printf("%d critical links\n", ans.size());
		sort(ans.begin(), ans.end());
		for (auto it : ans)
		{
			printf("%d - %d\n", min(it.first, it.second), max(it.first, it.second));
		}
	}
	return 0;
}

F - Ant Hills

题目链接:LightOJ - 1063
题目大意:求割点数量。
题解:参考P3388 【模板】割点(割顶)
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, n, m,dfn[N],lown[N],cnt[N];
vector<int>p[N];
void dfs(int u, int rt)
{
	dfn[u] =lown[u]= ++dfn[0];
	int sr = 0;
	for (auto to : p[u])
	{
		if (!dfn[to])
		{
			dfs(to, rt);
			lown[u] = min(lown[u], lown[to]);
			if (lown[to] >=dfn[u]&&u!=rt)cnt[u] = 1;
			if (u == rt)sr++;
			if (u == rt && sr >= 2)cnt[u] = 1;
		}
		lown[u] = min(dfn[to], lown[u]);
	}
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t); int qiu = 0;
	while (t--)
	{
		read(n), read(m);
		for (int i = 0; i <= n; i++)p[i].clear(), dfn[i] = 0,lown[i]=0,cnt[i]=0;
		for (int i = 1; i <= m; i++)
		{
			int x, y; read(x), read(y);
			p[x].push_back(y); p[y].push_back(x);
		}
		int ans = 0;
		for (int i = 1; i <= n; i++)
		{
			if (!dfn[i])dfs(i, i);
		}
		for (int i = 1; i <= n; i++)ans += cnt[i];
		printf("Case %d: %d\n", ++qiu, ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值