summer training 6-16
problem A:
给出一个物体的剖面图,判断其重心是在地基(图中房子的最后一行)的左边,中间平衡,右边,三种情况。
solution:
取每个房子的每个非空点(i, j)拆成两部分(i, j), (i, j + 1),然后每部分求和处于非空点个数,即可统计出重心,再与地基的最左最右端点比较,主页最右端点的特殊处理。
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200 + 5;
const int INF = 0x3f3f3f3f;
char s[maxn][maxn];
int n, m;
int main(){
scanf("%d%d",&n,&m);
for(int i = 0; i < n; ++i){
scanf("%s", s[i]);
}
double sumx = 0, leftx, rightx;
int cnt = 0, last = 0;
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j){
if(s[i][j] != '.'){
last = i;
++cnt;
sumx += (j * 1.0 + 0.5);
}
}
}
sumx = sumx / (cnt * 1.0);
for(int i = 0; i < m; ++i){
if(s[last][i] != '.'){
leftx = i * 1.0;
break ;
}
}
for(int i = m - 1; i >= 0; --i){
if(s[last][i] != '.'){
rightx = (i * 1.0) + 1.0;
break ;
}
}
if(sumx < leftx){
puts("left");
} else if(sumx > rightx){
puts("right");
} else{
puts("balanced");
}
return 0;
}
Problem B:
统计每个连通块的点个数,贪心选取最大的几个连通块使其点总数大于题目给定值h即可。
排序没有特判h=0的情况,wa到自闭,最后一发优先队列过去了。
AC code:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1014;
int dir1[6][2] = {-1, 0, -1, 1, 0, -1, 0, 1, 1, 0, 1, 1};
int dir2[6][2] = {1, -1, 1, 0, 0, 1, 0, -1, -1, -1, -1, 0};
char s[maxn][maxn];
int h,n,m;
int c = 0;
bool vis[maxn][maxn];
void dfs(int x, int y)
{
vis[x][y] = 1;
c++;
for(int i = 0; i < 6; ++i)
{
int nx = x + ((x % 2 == 1) ? dir1[i][0] : dir2[i][0]);
int ny = y + ((x % 2 == 1) ? dir1[i][1] : dir2[i][1]);
if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if(vis[nx][ny] || (s[nx][ny] == '#')) continue;
vis[nx][ny] = 1;
dfs(nx,ny);
}
}
bool cmp(const int a, const int b){
return a > b;
}
int main()
{
ios::sync_with_stdio(false);
priority_queue<int, vector<int>, less<int> > que;
cin >> h >> n >> m;
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < m; ++j)
cin >> s[i][j];
}
memset(vis, false, sizeof(vis));
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < m; ++j)
{
if(s[i][j]=='.' && !vis[i][j])
{
c = 0;
dfs(i,j);
que.push(c);
}
}
}
int sum =0, ans = 0;
while(!que.empty() && sum < h){
int top = que.top();
que.pop();
sum += top;
++ans;
}
cout << ans << endl;
return 0;
}
problem E:
给出一个n个点m条边的DAG,( 1 < n , m <= 1e6)每个点u有一个点权c[u], 每条边无边权,人从第0个点出发在DAG走完一圈,在每一个点都可以选择获得这个点权c[u], 但获得的点权和其第几次获取有关,第k次选择获得该点权c[u]的 1 / 2 ^(k - 1). 求人的可获得的最大点权 。
solution:
首先贪心(wa) , 正解:动态规划。
思路:每个点都可以选和不选。二维dp数组。dp[u][0], dp[u][1]表示该点的点权不选和选。
然后将图进行拓扑排序,在逆拓扑序列下利用如下动态方程:
dp[v][0] = max(dp[v][0], max(dp[u][0], dp[u][1]));
dp[v][1] = max(dp[v][1], max(dp[u][0] / 2.0 + (double)c[v], dp[u][1] / 2.0 + (double)c[v]));
其中u是v在逆向拓扑排序中的父亲结点。
最终答案是max(dp[0][0], dp[0][1])
算法时间复杂度:O(n + m)
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
struct edge{
int to, next;
}edge[maxn], invedge[maxn];
int head[maxn], tot, invhead[maxn], invtot;
int ind[maxn];
int c[maxn];
int topv[maxn];
double dp[maxn][2];
void init(int n){
tot = invtot = 0;
for(int i = 0; i <= n; ++i){
head[i] = invhead[i] = -1;
ind[i] = 0;
dp[i][0] = dp[i][1] = 0.0;
}
}
inline void addedge(const int u, const int v){
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
invedge[invtot].to = u;
invedge[invtot].next = invhead[v];
invhead[v] = invtot++;
}
void solve(int n){
int cnt = 0;
std::queue<int>q;
for(int i = 0; i < n; ++i){
if(!ind[i]){
q.push(i);
topv[cnt++] = i;
}
dp[i][1] = c[i];
}
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; ~i; i = edge[i].next){
int v = edge[i].to;
if(--ind[v] == 0){
q.push(v);
topv[cnt++] = v;
}
}
}
for(int i = cnt - 1; i >= 0; --i){
int u = topv[i];
for(int j = invhead[u]; ~j; j = invedge[j].next){
int v = invedge[j].to;
dp[v][0] = max(dp[v][0], max(dp[u][0], dp[u][1]));
dp[v][1] = max(dp[v][1], max(dp[u][0] / 2.0 + (double)c[v], dp[u][1] / 2.0 + (double)c[v]));
}
}
double ans = max(dp[0][0], dp[0][1]);
printf("%.6lf\n", ans);
}
int main(){
int n, m;
scanf("%d%d",&n, &m);
for(int i = 0; i < n; ++i){
scanf("%d", &c[i]);
}
init(n);
int u,v;
for(int i = 0; i < m; ++i){
scanf("%d%d",&u, &v);
addedge(u, v);
++ind[v];
}
solve(n);
return 0;
}
problem H:
给出k个形如ACxHy的化学分子合成形如AnHmCm的化学分子,问最多可以合成多少个。
solution:
简单贪心,按照题意模拟即可。
AC code:
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 3000;
char s[maxn];
int k;
long long a[30], b[30];
long long c[30];
bool checkup(const char c){
if(c >= 'A' && c <= 'Z') return true;
return false;
}
bool checknum(const char c){
if(c >= '0' && c <= '9') return true;
return false;
}
void getinit(char *s, long long *a){
memset(a, 0, sizeof(a));
long long num = 0;
bool ok = true;
char c = a[0];
int n = strlen(s);
for(int i = 0; i < n; ++i){
if(checkup(s[i]) && ( (i + 1 < n && checkup(s[i + 1]) ) || i == n - 1)){
++a[s[i] - 'A'];
} else if(checkup(s[i]) && ( i + 1 < n &&checknum(s[i + 1]))){
c = s[i];
} else if(checknum(s[i]) && ((i + 1 < n && checkup(s[i + 1]) ) || i == n - 1)){
num = num * 10 + (s[i] - '0');
a[c - 'A'] += num;
num = 0;
} else{
num = num * 10 + (s[i] - '0');
}
}
}
int main(){
scanf("%s%d",s, &k);
getinit(s, a);
scanf("%s",s);
getinit(s, b);
memset(c, 0, sizeof(c));
for(int i = 0; i < 30; ++i){
if(a[i] && b[i]){
//cout << a[i] << " " << b[i] << endl;
c[i] = (1LL * a[i] * k) / b[i];
}
}
long long maxx = inf;
for(int i = 0; i < 30; ++i){
if(b[i]){
maxx = min(c[i], maxx);
}
}
if(maxx == inf){
cout << 0 << endl;
} else{
cout << maxx << endl;
}
return 0;
}
problem I
给定一个字符串,将其切块成最多部分的回文串。
eg:013189301 spilt: 01|3|189|3|01 -> k = 5
solution:
从两边设置指针贪心选取最小的相等部分即可。 特判中间相等只有一个字符串和中间有一部分没有计算的情况。
计算相等方法,字符串哈希预处理后便是O(1),下面我采用了自然溢出的方法。
AC code:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e6 + 10;
const int p = 11;
char s[maxn];
ull pw[maxn];
ull Hash[maxn];
int main(){
while(~scanf("%s", s)){
Hash[0] = 0;
pw[0] = 1;
int len = strlen(s);
for(int i = 1; i <= len; ++i){
Hash[i] = Hash[i - 1] * p + (s[i - 1] - '0');
pw[i] = pw[i - 1] * p;
}
ull ans = 0;
int mid = (len % 2 == 0) ? (len / 2) : (len / 2 + 1);
int st = 0, ed = 1;
for( ; ed <= mid; ++ed){
if((Hash[ed] - Hash[st] * pw[ed - st]) == (Hash[len - st] - Hash[len - ed] * pw[ed - st])){
if(ed != len - st){
ans += 2;
st = ed;
} else{
++ans;
}
}
}
if(len - 2 * st > 1 || ans == 0){
++ans;
}
cout << ans << endl;
}
return 0;
}
problem J:
给出n(<1000)个长度为k(<10)的字符串, 字符串i和j的不同度就是i和j的之间的点权。然后求一颗最小生成树。(暖场签到,太久没写并查集,写错了gg)
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 5;
const int maxm = maxn * maxn;
int fa[maxn];
int Find(int x){
return (x == fa[x]) ? x : (fa[x] = Find(fa[x]));
}
bool Union(int x, int y){
x = Find(x), y = Find(y);
if(x != y){
fa[y] = x;
return true;
}
return false;
}
struct Edge{
int from, to, next, w;
bool operator < (const Edge &b){
return w < b.w;
}
}edge[maxm];
int head[maxn], tot;
inline void addedge(const int u, const int v, const int w){
edge[tot].from = u;
edge[tot].to = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot++;
}
void init(){
tot = 0;
memset(head, -1, sizeof(head));
for(int i = 0; i < maxn; ++i) fa[i] = i;
}
vector<pair<int,int> >vec;
void Krukal(int n){
sort(edge, edge + tot);
long long sum = 0;
if(vec.size() > 0){
vec.clear();
}
int cnt = 0;
for(int i = 0; i < tot; ++i){
int u = edge[i].from, v = edge[i].to;
if(Union(u, v)){
++cnt;
sum += edge[i].w;
vec.push_back(make_pair(u, v));
if(cnt == n - 1) break ;
}
}
cout << sum << endl;
for(int i = 0; i < vec.size(); ++i){
printf("%d %d\n", vec[i].first, vec[i].second);
}
}
char s[maxn][11];
int getw(const char *a, const char *b){
int w = 0;
for(int i = 0; a[i]; ++i){
if(a[i] != b[i]) ++w;
}
return w;
}
int main(){
init();
int n, k;
scanf("%d%d", &n, &k);
for(int i = 0; i < n; ++i){
scanf("%s", s[i]);
}
for(int i = 0; i < n; ++i){
for(int j = i + 1; j < n; ++j){
int w = getw(s[i], s[j]);
//cout << i << " " << j << " " << w << endl;
addedge(i, j, w);
}
}
Krukal(n);
return 0;
}