参考博文: 【题目自解】北京大学2018计算机学科夏令营上机考试
题目名称 | 题目类型 | 测试情况 |
---|---|---|
A 计算两个日期之间的天数 | 模拟题 | AC |
B 回文子串 | 模拟题 | AC |
C The Die Is Cast | BFS | |
D Euro Efficiency | DP | |
E 重要逆序对 | 模拟题/归并 | WR |
F Tram | 最短路 | AC |
G 食物链 | 并查集 | WR |
H DFS spanning tree |
A 计算两个日期之间的天数
思路:输入sy sm sd ey em ed。则先计算sy-1-1到ey-1-1的天数之差,然后再计算减去在sy年中的天数并加上在ey年中的天数。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int P_months[15] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int sy, sm, sd;
int ey, em, ed;
bool is_R(int year)
{
if(year%100==0 && year%400!=0) return 0;
if(year%4==0) return 1;
return 0;
}
int main()
{
cin >> sy >> sm >> sd;
cin >> ey >> em >> ed;
int year = sy, flagm = 1, R = 0;
int sumy = 0, sum1 = 0, sum2 = 0;
//模拟得打sy-1-1到ey-1-1的天数之差
while(year<ey)
{
if(is_R(year)) sumy += 366;
else sumy += 365;
year++;
}
int ed1 = sm, ed2 = em;
//得到sm-sd的天数
for(int i=0; i<ed1-1; i++)
{
sum1 += P_months[i];
if(i==1 && is_R(sy)) sum1 += 1;
}
sum1 += sd;
//得到em-ed的天数
for(int i=0; i<ed2-1; i++)
{
sum2 += P_months[i];
if(i==1 && is_R(ey)) sum2 += 1;
}
sum2 += ed;
//sumy加上em-ed的天数并减去sm-sd的天数
cout << sumy + sum2 - sum1 << endl;
return 0;
}
B 回文子串
思路:本题目简单在数据范围不大,不超过500,则可以使用n^2的复杂度。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
vector<string> v;
//检验是否是回文串
void is_H(string s, int st, int len)
{
string ans = "";
int l = st, r = st+len-1, flag = 1;
if(r>s.size()-1) return;
while(l<r)
{
if(s[l]!=s[r])
{
flag = 0;
break;
}
l++, r--;
}
if(flag)
{
for(int i=st; i<st+len; i++)
ans += s[i];
}
else return;
cout << ans << endl;
}
int main()
{
string s;
cin >> s;
for(int i=2; i<=s.size(); i++)//i是回文串的长度
{
for(int st = 0; st<=s.size()-i; st++)//st是回文串起始的下标
{
is_H(s, st, i);
}
}
return 0;
}
C The Die Is Cast
思路:我使用了双层BFS,找到后先BFS遍历所有非’.'的位置,在BFS * 的过程中,如果遍历到未遍历的X则在X的基础上BFS遍历X,并替换遍历到的X为。记录开始遍历X的次数。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
int w, h;
string G[55];
int vis[55][55];
int dx[5] = {0, 0, 1, -1};
int dy[5] = {1, -1, 0, 0};
vector<int> v;
struct Node
{
int x, y;
Node(int x=-1, int y=-1):x(x), y(y){}
};
void bfs1(Node st)
{
queue<Node> q1;
q1.push(st);
G[st.x][st.y] = '*';//替换,方便之后遍历
while(!q1.empty())
{
Node u = q1.front(); q1.pop();
for(int i=0; i<4; i++)
{
int x = u.x+dx[i], y = u.y+dy[i];
if(x<0 || x>=h || y<0 || y>=w) continue;
if(!vis[x][y] && G[x][y]=='X')
{
q1.push(Node(x, y));
G[x][y] = '*';
}
}
}
}
void bfs0(Node st)
{
int sum = 0;
queue<Node> q;
q.push(st);
vis[st.x][st.y] = 1;
while(!q.empty())
{
Node u = q.front(); q.pop();
for(int i=0; i<4; i++)
{
int x = u.x+dx[i], y = u.y+dy[i];
if(x<0 || x>=h || y<0 || y>=w) continue;
//注意:X可能会阻断*,所以需要先处理X然后将X变为*,使得整个图依然连通
if(!vis[x][y] && G[x][y]=='X')
{
bfs1(Node(x, y));
q.push(Node(x, y));
sum++;
}
if(!vis[x][y] && G[x][y]=='*')
{
vis[x][y] = 1;
q.push(Node(x, y));
}
}
}
v.push_back(sum);
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("out.txt","w",stdout);
int kase = 1;
while(scanf("%d%d", &w, &h)!=EOF)
{
getchar();
if(w+h==0) break;
for(int i=0; i<h; i++)
getline(cin, G[i]);
v.clear();
memset(vis, 0, sizeof(vis));
for(int i=0; i<h; i++)
{
for(int j=0; j<w; j++)
{
if(G[i][j]=='*' && !vis[i][j])
{
bfs0(Node(i, j));
}
}
}
printf("Throw %d\n", kase++);
sort(v.begin(), v.end());
for(int i=0; i<v.size(); i++)
{
if(i) printf(" ");
printf("%d", v[i]);
}
printf("\n\n");
}
return 0;
}
D Euro Efficiency
参考博文:POJ 1252 Euro Efficiency(完全背包, 找零问题, 二次DP)
思路:首先可以得知是完全背包,且是找零问题。可以使用两次DP来解决问题。第一次求没有找零时的状态值,第二次当找零时更新状态值。
- 状态:令 dp[v] 表示拼出总面额为 v 的钱所需要的最少硬币数。
- 状态转移方程:求解一步完全背包, 这一步假设不允许找零, dp[v] = min(dp[v], dp[v-w[i]]+1),(第i个硬币可以使用或不使用)。
求解找零的最优解, dp[v] = min(dp[v], dp[v+w[i]]+1),(表示使用第i个硬币找零或不找零)。 - 初始化:dp[*] = INF。
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 110;
const int MAXM = 100*20;
const int n = 6;
int money[MAXN];
int dp[MAXM+10];
/*
* 不允许找钱, 前 i 个 coin 相加恰好为 x money
* 最大的money 总额可能会超过 100
* 完全背包, 每件物品可以放任意多次, 第二层循环(j) 从小到大遍历
*/
void withoutChange() {
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0; // "恰好"
for(int i = 0; i < n; i++) {
for(int j = money[i]; j <= MAXM; j ++) {
dp[j] = min(dp[j], dp[j-money[i]]+1);
}
}
}
/*
* 允许找钱
* 对 i, 枚举找回的钱
* 允许找钱, 同时意味着一方给的钱可能会超过 100, 题目已经说明, 每组数据都包含1, 所以, 数据若是比较苛刻的话, maxn 得设置为 99 * 50
* 状态转移方程的方向变了, 同时完全背包要求等式右边是新的数据, 因此, j 的遍历顺序为从大到小
* dp[v] = min(dp[v], dp[v+w[i]]) 对于第 i 个硬币, 要么作为零钱, 要么不作为, 反过来, 好难想
*/
void withChange() {
for(int i = 0; i < n; i ++) {
for(int j = MAXM-money[i]; j >= 1; j --) {
dp[j] = min(dp[j], dp[j+money[i]]+1);
}
}
}
int main() {
//freopen("E:\\Copy\\ACM\\测试用例\\in.txt", "r", stdin);
int testCase;
scanf("%d", &testCase);
while(testCase--) {
for(int i = 0; i < n; i ++) {
scanf("%d", &money[i]);
}
float avg_coin = 0.0;
int max_coin = 0;
// first step
withoutChange();
// second step
withChange();
for(int i = 1; i <= 100; i ++) {
avg_coin += dp[i];
max_coin = max(max_coin, dp[i]);
}
avg_coin = avg_coin/100;
printf("%0.2f %d\n", avg_coin, max_coin);
}
return 0;
}
E 重要逆序对
思路:本题难在数据范围比较大,不能使用n^2的复杂度。
参考博文:2018计算机学科夏令营上机考试E:重要逆序对(归并排序)
代码:
F Tram
思路:图论中最短路,主要在邻接矩阵的构造。在邻接矩阵的构建过程当中,不需要旋转的路径设置为0,需要旋转的路径设置为1,不可达的路径为-1,然后采用dijstra算法即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX = 105;
const int INF = 1<<29;
int G[MAX][MAX], d[MAX], vis[MAX], N;
int dijstra(int A, int B)
{
if(A==B) return 0;
fill(d, d+MAX, INF);
memset(vis, 0, sizeof(vis));
d[A] = 0;
while(1)
{
int Min = INF, u;
for(int i=1; i<=N; i++)
{
if(!vis[i] && Min>d[i])
{
u = i;
Min = d[i];
}
}
if(Min==INF) return -1;
if(u==B) return Min;
vis[u] = 1;
for(int i=1; i<=N; i++)
{
if(!vis[i] && G[u][i]!=-1 && d[i]>d[u]+G[u][i])
{
d[i] = d[u]+G[u][i];
}
}
}
}
int main()
{
int A, B, m, a;
memset(G, -1, sizeof(G));
cin >> N >> A >> B;
for(int i=1; i<=N; i++)
{
cin >> m;
for(int j=1; j<=m; j++)
{
cin >> a;
if(j>1) G[i][a] = 1;
else G[i][a] = 0;
}
}
cout << dijstra(A, B) << endl;
return 0;
}
G 食物链
思路:这道题之前在POJ上做了好多遍,本次之所以WR,居然是一个小括号括错了位置,可见在机试中任何情况都有可能出现,一定要避免不必要的失误。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int M = 50001;
const int MAX = 3*M;
int father[MAX], Rank[MAX];
void Init(int MX)
{
for(int i=0; i<=MX; i++)
{
father[i] = i;
Rank[i] = 1;
}
}
int findRoot(int x)
{
int r = x;
while(r!=father[r]) r = father[r];
int j = x, i;
while(j!=r)
{
i = father[j];
father[j] = r;
j = i;
}
return r;
}
void join(int x, int y)
{
int fx = findRoot(x), fy = findRoot(y);
if(fx!=fy)
{
if(Rank[fx]>Rank[fy]) father[fy] = fx;
else father[fx] = fy;
if(Rank[fx]==Rank[fy]) Rank[fy]++;
}
}
bool same(int x, int y)
{
return findRoot(x)==findRoot(y);
}
int main()
{
int N, K, D, X, Y;
int sum = 0;
cin >> N >> K;
int MX = 3*N;
Init(MX);
while(K--)
{
cin >> D >> X >> Y;
if(X<=0 || Y<=0 || X>N || Y>N) sum++;
else if(D==1)
{
if(same(X, Y+N) || same(X+N, Y))
{
sum++;
continue;
}
join(X, Y);
join(X+N, Y+N);
join(X+2*N, Y+2*N);
}
else if(D==2)
{
if(same(X, Y) || same(X+N, Y))
{
sum++;
continue;
}
join(X, Y+N);
join(X+N, Y+2*N);
join(X+2*N, Y);
}
}
printf("%d\n", sum);
return 0;
}