A. Maze
题意概述: 给出
n×m (1≤n,m≤500)
n
×
m
(
1
≤
n
,
m
≤
500
)
地图,其中 .
是空白处,#
是障碍处, 其中有
s
s
个 .
构成一整块连通块,现在要你在空白处加 个障碍 #
使得地图的连通块数不变。
题解: 对空白处 DFS ,回溯的时候填满 k k 块即可。考察对DFS的理解。
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int dir[4][2] = { {1, 0}, {0, 1}, {-1, 0}, {0, -1} };
char s[505][505];
bool vis[505][505];
int n, m, k;
void dfs(int x, int y) {
vis[x][y] = 1;
for (int i = 0; i < 4; i++) {
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if (dx >= 1 && dx <= n && dy >= 1 && dy <= m && !vis[dx][dy] && s[dx][dy] == '.') {
dfs(dx, dy);
}
}
if (k > 0) {
s[x][y] = 'X';
k--;
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
bool ok = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i][j] == '.' && !vis[i][j]) {
dfs(i, j);
ok = 1;
break;
}
}
if (ok) break;
}
for (int i = 1; i <= n; i++) puts(s[i] + 1);
return 0;
}
B. Office Keys
题意概述: 数轴上有 个人, k (0≤k≤103) k ( 0 ≤ k ≤ 10 3 ) 把钥匙,每个人都要拿一把钥匙然后到办公室 p (0≤p≤109) p ( 0 ≤ p ≤ 10 9 ) ,( n,p,k n , p , k 均给出坐标),每个人拿的钥匙不能相同。在数轴上移动一个单位的时间是 1 1 ,则所有人都拿到钥匙到达办公室的最短时间是多少?
题解: 我们分别读入和 b[1...k] b [ 1... k ] ,排序后观察,对于 ai≤aj a i ≤ a j , bk≤bm b k ≤ b m ,如果将这两把钥匙分配给这两个人,要分配方式最优只可能 ai→bk,aj→bm a i → b k , a j → b m ,因为如果 ai→bm,aj→bk a i → b m , a j → b k ,那么最大的花费时间是 max(|ai−bm|+|bm−p|,|aj−bk|+|bk−p|) m a x ( | a i − b m | + | b m − p | , | a j − b k | + | b k − p | ) ,显然小于前者 max(|ai−bk|+|bk−p|,|aj−bm|+|bm−p|) m a x ( | a i − b k | + | b k − p | , | a j − b m | + | b m − p | ) ,白话去理解就是离门最近的人去拿最远的钥匙是不可取的。
- 根据这样分配性质,我们完全可以考虑二分答案+贪心分配,每个人都去取离他最左侧钥匙就行!这样做复杂度 O(nklogm) O ( n k l o g m ) 其中 m m 是钥匙最大长度。
- 有了这个性质,我们容易发现这就符合线性dp的最优子结构啊,然后 和 k k 的数据范围也很开心,复杂度!(这里当做扩展,有兴趣的选手在学完动态规划以后可以来重新AC一下这道题)
参考代码:
- 二分写法
#include <bits/stdc++.h>
#define N 1111
int a[N], b[N << 1], n, k, p;
bool check(ll x)
{
int cnt = 0, next = 1;
for (int i = 1; i <= n; i++)
for (int j = next; j <= k; j++)
if (abs(a[i] - b[j]) + abs(b[j] - p) <= x)
{
next = j + 1;
cnt++;
break;
}
return cnt == n;
}
int main()
{
scanf("%d%d%d", &n, &k, &p);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= k; i++)
scanf("%d", &b[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + k + 1);
ll l = -1, r = INF, ans = 0;
while (l <= r)
{
ll mid = l + r >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
printf("%I64d\n", ans);
return 0;
}
- dp写法
#include <bits/stdc++.h>
#define N 1111
int a[N], b[N << 1], dp[N][N << 1];
int main()
{
int n, k, p;
scanf("%d%d%d", &n, &k, &p);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= k; i++)
scanf("%d", &b[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + k + 1);
dp[0][0] = 0;
for (int i = 1; i <= k; i++)
dp[0][i] = 0;
for (int i = 1; i <= n; i++)
{
dp[i][0] = INF;
for (int j = 1; j <= k; j++)
dp[i][j] = max(abs(a[i] - b[j]) + abs(b[j] - p), dp[i - 1][j - 1]);
for (int j = 1; j <= k; j++)
dp[i][j] = min(dp[i][j], dp[i][j - 1]);
}
printf("%d\n", dp[n][k]);
return 0;
}
C. Combination Lock
题目描述: 给出两个 n (1≤n≤103) n ( 1 ≤ n ≤ 10 3 ) 位数,每次操作可以把第一个数的第 i i 位数字 变成 ai→(ai+1) a i → ( a i + 1 ) ,问至少进行几次操作能使第一个数变成第二个数?
题解: 直接模拟每一位,贪心地取代价的最小值 min(step,10−step) m i n ( s t e p , 10 − s t e p ) 即可。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
/* head */
char a[maxm], b[maxm];
int main() {
int n;
scanf("%d", &n);
scanf("%s%s", a, b);
int ans = 0;
rep(i, 0, n) {
int x = a[i] - '0';
int y = b[i] - '0';
int z = max(x, y) - min(x, y);
ans += min(z, 10 - z);
}
printf("%d\n", ans);
return 0;
}
D. Two Buttons
题目描述: 给两个数字 n,m (1≤n.m≤104) n , m ( 1 ≤ n . m ≤ 10 4 ) 有两种操作
- n=n×2 n = n × 2
- n=n−1 n = n − 1
问最少进行几次操作得到 n==m n == m ?
题解: 考虑 n n 和 都很小,不难想到搜索,DFS、BFS都可,考察搜索。
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
/* head */
struct node {
int x, step;
friend bool operator< (const node& a, const node& b) { return a.step > b.step; }
};
bool vis[maxn];
int main() {
int n, m;
scanf("%d%d", &n, &m);
priority_queue<node> q;
q.push({n, 0});
vis[n] = 1;
int ans = -1;
while (!q.empty()) {
node cur = q.top(); q.pop();
vis[cur.x] = 1;
if (cur.x == m) {
ans = cur.step;
break;
}
int a = cur.x << 1;
int b = cur.x - 1;
if (a <= m * 2 && !vis[a]) q.push({a, cur.step + 1});
if (1 <= b && !vis[b]) q.push({b, cur.step + 1});
}
printf("%d\n", ans);
return 0;
}
E. Karen and Game
题意概述: 有一个 n×m (1≤n,m≤100) n × m ( 1 ≤ n , m ≤ 100 ) 的矩阵,每次可以对某行或某列所有元素都减去一(如果某个元素已经减到了 0 0 则后续不能对该行与该列操作),问是否存在一种删法使得整个矩阵都变成 ,输出最小步数,无解则输出 −1 − 1 。
题解: 最优的肯定是每次围绕那个最小的元素操作,因此考虑从行数和列数较小的地方开始删即可,考察贪心思想。
参考代码:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 111
int mp[N][N];
vector<int> ansC, ansR;
int n, m, sum1, sum2;
void solve(bool Row)
{
if (Row)
{
for (int i = 1; i <= n; i++)
{
int cut = mp[i][1];
for (int j = 2; j <= m; j++)
cut = min(cut, mp[i][j]);
for (int j = 1; j <= m; j++)
mp[i][j] -= cut;
sum2 += cut * m;
while (cut--)
ansR.push_back(i);
}
for (int i = 1; i <= m; i++)
{
int cut = mp[1][i];
for (int j = 2; j <= n; j++)
cut = min(cut, mp[j][i]);
for (int j = 1; j <= n; j++)
mp[j][i] -= cut;
sum2 += cut * n;
while (cut--)
ansC.push_back(i);
}
}
else
{
for (int i = 1; i <= m; i++)
{
int cut = mp[1][i];
for (int j = 2; j <= n; j++)
cut = min(cut, mp[j][i]);
for (int j = 1; j <= n; j++)
mp[j][i] -= cut;
sum2 += cut * n;
while (cut--)
ansC.push_back(i);
}
for (int i = 1; i <= n; i++)
{
int cut = mp[i][1];
for (int j = 2; j <= m; j++)
cut = min(cut, mp[i][j]);
for (int j = 1; j <= m; j++)
mp[i][j] -= cut;
sum2 += cut * m;
while (cut--)
ansR.push_back(i);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
scanf("%d", &mp[i][j]);
sum1 += mp[i][j];
}
sum2 = 0;
solve(n <= m);
if (sum1 != sum2)
puts("-1");
else
{
int szR = ansR.size(), szC = ansC.size();
printf("%d\n", szC + szR);
for (int i = 0; i < szR; i++)
printf("row %d\n", ansR[i]);
for (int i = 0; i < szC; i++)
printf("col %d\n", ansC[i]);
}
return 0;
}
F. Online Courses In BSU
题意概述: 你有 n (1≤n≤105) n ( 1 ≤ n ≤ 10 5 ) 门课,分别给出要修这 n n 门课之前必须修完的前置课程。给出一个人的必修课问他最少需要修几门课以及修课的顺序。若无法修完,则输出 。
题解: 有先后顺序问题,可以根据关系构造一个图,在dfs时记录经过课程的顺序。当一个需要修的课程的某个前置课程的前置课程是其本身时,该人无法修完,因此在dfs的时候记录一下到当前位置为止经过了哪些课程,如果继续经过这些课程,就无法修完。考察建图。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define SZ(x) ((int)(x).size())
#define rep(i,a,n) for (int i=a;i<n;i++)
typedef vector<int> VI;
const int maxn = 1e5 + 10;
/* head */
VI g[maxn], ans;
int a[maxn];
bool vis[maxn], over[maxn], ok = 1;
void dfs(int u) {
if (vis[u]) ok = 0;
if (!ok) return;
vis[u] = 1;
rep(i, 0, SZ(g[u])) {
int v = g[u][i];
if (!over[v]) {
dfs(v);
}
}
ans.pb(u);
over[u] = 1;
}
inline void solve() {
int n, k;
scanf("%d%d", &n, &k);
rep(i, 1, k + 1) scanf("%d", &a[i]);
rep(i, 1, n + 1) {
int t;
scanf("%d", &t);
while (t‐‐) {
int j;
scanf("%d", &j);
g[i].pb(j);
}
}
rep(i, 1, k + 1) {
if (!over[a[i]]) dfs(a[i]);
if (!ok) {
puts("‐1");
return;
}
}
printf("%d\n", SZ(ans));
rep(i, 0, SZ(ans)) {
if (i == 0) printf("%d", ans[i]);
else printf(" %d", ans[i]);
}
}
int main() {
solve();
return 0;
}
G. Random Query
题意概述: 给一个长度为 n (1≤n≤106) n ( 1 ≤ n ≤ 10 6 ) 的数组 a[1...n] (1≤ai≤109) a [ 1... n ] ( 1 ≤ a i ≤ 10 9 ) ,随机从 1 1 到 中取两个数(可相等),记其中较小的数为 l l ,较大的数为 ,求数组 a a 中位于区间 中不同元素的个数的期望。
题解: 对每个元素,单独考虑其对答案的贡献。对于一段区间中重复出现的数字,可认为是其第一次出现时,即最左边的数字做的贡献。取法一共有 n×n n × n 种(注: l=1,r=2 l = 1 , r = 2 有 12 12 和 21 21 两种取法,但 l=r=1 l = r = 1 只有 11 11 一种取法),接下来考虑单个元素 ai a i ,其对答案有贡献的取法有多少种。对答案有贡献,等价于其在区间内且该数是区间内第一次出现,因此 l l 的可取值即为从当前位置开始,向左直到该数再次出现或到数组头, 的可取值即为从当前位置开始,向右直到数组尾。记当前位置为 x x ,该数上一次出现位置为 ,则该元素对答案有贡献的取法种类为 2·(x−p)·(n−x+1)−1 2 · ( x − p ) · ( n − x + 1 ) − 1 ,对答案的贡献即为 2(x−p)(n−x+1)−1n2 2 ( x − p ) ( n − x + 1 ) − 1 n 2 。小优化,考虑到 1≤ai≤106 1 ≤ a i ≤ 10 6 ,可开 106 10 6 的数组存下每个数出现的位置,就可 O(1) O ( 1 ) 访问该数上一次出现的位置。
参考代码:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
int n, a, b[1000010];
double ans;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a);
ans += (2.0 * (i - b[a]) * (n - i + 1) - 1) / n / n;
b[a] = i;
}
printf("%.6f\n", ans);
return 0;
}
H. Company Income Growth
题意概述: 给出 2001 2001 到 2000+n (1≤n≤100) 2000 + n ( 1 ≤ n ≤ 100 ) 年的每年账单序列,要你找出最长的从 1 1 开始单步上升(每次步长为1)的序列长度,并输出个个年份。
题解: 看看 才多少?你应该知道怎么做了。
#include <bits/stdc++.h>
using namespace std;
/* head */
int main() {
int n;
cin >> n;
vector <int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
vector <int> res;
int i = 0, c = 1;
while (i < n) {
if (a[i] == c) {
res.push_back(2000 + i + 1);
c++;
} else i++;
}
if (res.size() == 0)
cout << 0 << endl;
else {
cout << res.size() << endl;
for (int i = 0; i < res.size(); i++)
cout << res[i] << " ";
}
cout << endl;
return 0;
}
I. New Year Snowmen
题意概述: 每个雪人由三个不同大小的雪球做成(大中小且不等),现给出 n(1≤n≤105) n ( 1 ≤ n ≤ 10 5 ) 个半径为 ri(1≤ri≤109) r i ( 1 ≤ r i ≤ 10 9 ) 的雪球,问最多能做出多少个雪球,并分别输出每个雪人的三个雪球的半径。
题解: 二分答案,判断mid是否可行时每次贪心地先把最大的mid个数放在最下层,然后再放第二波mid大的数,然后再放第三波mid大的数,中间遇到不符的直接跳过那个数。同时每次记录方案。
参考代码:
#include<bits/stdc++.h>
const int N = 1e5 + 10;
using namespace std;
int n;
int a[N], b[N], c[N], s[N]; // a,b,c是中间用来判断的变量数组
int aa[N], bb[N], cc[N];
int ok(int mid) {
int i = n, j = 1;
for(; j <= mid; i--, j++) a[j] = s[i]; //底层
j = 1;
while(j <= mid && i >= 1) {
if(s[i] < a[j]) b[j] = s[i], j++; //中层
i--;
}
if(i < 1) return 0;
j = 1;
while(j <= mid && i >= 1) {
if(s[i] < b[j]) c[j] = s[i], j++; //上层
i--;
}
if(j != mid + 1) return 0;
for(int k = 1; k <= mid; k++) aa[k] = a[k], bb[k] = b[k], cc[k] = c[k]; //记录答案
return 1;
}
int main() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> s[i];
sort(s + 1, s + 1 + n);
int l = 0, r = n / 3, mid, ans = 0;
while(l <= r) {
int mid = (l + r) / 2;
if(ok(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
cout << ans << endl;
for(int i = 1; i <= ans; i++) cout << aa[i] << " " << bb[i] << " " << cc[i] << endl;
return 0;
}
J. Chain Reaction
题意概述: 一条马路上有 n (1≤n≤105) n ( 1 ≤ n ≤ 10 5 ) 个灯塔,给出每个灯塔的位置和射程范围,激活的灯塔左侧在射程范围内的灯塔被摧毁,被摧毁的灯塔不会激活,现在在最右侧添加一个任意位置任意射程范围的灯塔,从最右侧开始激发,未被摧毁的灯塔继续激发,问被摧毁灯塔的最小值是多少?
题解: 贪心的思路,对于每个灯塔计算价值 vi=1−sum v i = 1 − s u m 其中 sum s u m 为该灯塔射程范围内灯塔的价值之和(对于射程范围可以去二分),即为保留至该灯塔可以获得的收益, 那么 vi v i 的前缀和的最大值就是灯塔保留数量的最大值!所以用 n n 减去最大值就是答案。考察二分的熟练运用。
参考代码:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define maxn 101000
using namespace std;
typedef long long ll;
int ans[maxn];
pair<int, int> p[maxn];
int main()
{
int n;
while (~scanf("%d", &n))
{
for (int i = 1; i <= n; i++)
scanf("%d%d", &p[i].first, &p[i].second);
sort(p + 1, p + n + 1);
p[n + 1].first = -1;
ans[0] = 0;
int res = INF;
for (int i = 1; i <= n; i++)
{
int last_live = lower_bound(p + 1, p + i, make_pair(p[i].first - p[i].second, -1)) - (p + 1);
ans[i] = ans[last_live] + 1;
res = min(res, n - ans[i]);
}
printf("%d\n", res);
}
return 0;
}
K. Sereja and Prefixes
题目描述: 现在有一个数字串,初始为空。有 种操作:
- 第一种是在串的末尾加一个数字 x x ;
- 第二种是在串的末尾加上当前串前 个数字,而且加 c c 次;
现在先进行 次操作,然后询问 n n 次,每次询问现在这个串的第 个数字是多少。
保证 1≤m,n≤105,1≤x≤105,1≤l≤105,1≤c≤104,1≤t≤1014 1 ≤ m , n ≤ 10 5 , 1 ≤ x ≤ 10 5 , 1 ≤ l ≤ 10 5 , 1 ≤ c ≤ 10 4 , 1 ≤ t ≤ 10 14
题解: 询问数据会非常大,这是因为第二种操作一次可以在后面补上最多 109 10 9 个数字,而操作数又有 105 10 5 个。但是每次操作在后面补上的数字大部分是重叠的,而且 l≤105 l ≤ 10 5 ,所以所有操作二加上的数字只可能来自前 105 10 5 个数字。因此只维护前 105 10 5 个数字,考虑后面部分:对于操作一,直接记录在后面这个地方出现了一个新数字。对于操作二,记录一下 l l 和 的数据即可。这样可以知道第 i i 次操作之后当前串的长度 。显然添加数字的操作使 len[i] l e n [ i ] 单调递增。询问时,如果 t≤105 t ≤ 10 5 直接输出,否则二分询问的数字是在第几次操作中被添加的,对于操作一,直接输出它。对于操作二,已知了 l l 和 ,直接在前 105 10 5 个数字中找即可。
参考代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll now;
ll s[100010];
int a[100010];
int v[100010];
int w[100010];
int opp[100010];
int len=100000;
int n,m,q,op,x,y;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&op);
if (op==1)
{
scanf("%d",&x);
now++;
if (now>len)
{
opp[++m]=1;
v[m]=x;
s[m]=now-1;
}else a[now]=x;
}else
{
scanf("%d%d",&x,&y);
now+=(ll)x*y;
if (now>len)
{
opp[++m]=2;
v[m]=x;w[m]=y;
s[m]=now-x*y;
}
if(now-x*y<=len)
{
now-=x*y;
for (int i=1;i<=y;i++)
for (int j=1;j<=x;j++)
if (now+(i-1)*x+j<=len)
a[now+(i-1)*x+j]=a[j];
else break;
now+=x*y;
}
}
}
scanf("%d",&q);
while (q--)
{
ll t;scanf("%lld",&t);
if (t<=len){printf("%d ",a[t]);continue;}
int l=1,r=m,pos=m;
while (l<=r)
{
int mid=(l+r)>>1;
if (t>s[mid])pos=mid,l=mid+1;
else r=mid-1;
}
if (opp[pos]==1)printf("%d",a[v[pos]]);
else
{
int pp=v[pos],qq=w[pos];
ll des=t-s[pos];des%=pp;
if (des==0)des=pp;
printf("%d",a[des]);
}
printf(" ");
}
puts("");
return 0;
}