文章目录
前言
本次训练的内容包含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
1≤L<R≤2∗109
题解:数位
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
0≤x≤y≤1018,0<T≤30
题解:蛮有思维的数位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=right−left,这样数组就可以少一维,这样就完全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
2−SAT,然后写一下这题。这题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;
}