马克飞象莫名其妙无法同步到印象笔记,现在这里保存一下吧。
Training Day2
@(ACM集训)
A.How far away ?(Tarjan LCA)
题意:
无向图,给定边及边权重,任意两点之间都有一条唯一的道路,道路上每个点只能出现一次。给定询问,求询问的结点之间的距离。
分析:
路上每个点只能出现一次,可以转化成有根树,问题也即为求最近公共祖先问题~~ 这里每条边加上了距离,求出LCA后,用 u、v 的距离根的距离和减去 2 倍的根到最近公共祖先的距离即可,然后就是tarjan LCA 模板题了。
代码:
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
#define fuck cout<<"fuck"<<endl;
const int maxn = 40005, maxq = 205;
typedef pair<int, int>p;
vector<p>G[maxn];
#define fi first
#define se second
int pa[maxn];
bool vis[maxn];
bool in[maxn];
int ance[maxn];
int dist[maxn];
int n, tot;
int head[maxn];
int ans[maxn];
struct Query{int to, next, index;};
Query query[maxq * 2];
void add_query(int u, int v, int index)
{
query[tot].to = v;
query[tot].index = index;
query[tot].next = head[u];
head[u] = tot++;
query[tot].to = u;
query[tot].index = index;
query[tot].next = head[v];
head[v] = tot++;
}
int _find(int x)
{
if(pa[x] != x) return pa[x] = _find(pa[x]);
return x;
}
void unite(int x, int y)
{
int rx = _find(x), ry = _find(y);
if(rx == ry) return;
pa[rx] = ry;
}
void init()
{
tot = 0;
for(int i = 1; i <= n; i++){
G[i].clear();
pa[i] = i;
}
mem(ance, 0);
mem(vis, false);
mem(head, -1);
mem(dist, 0);
mem(in, false);
mem(ans, 0);
}
void LCA(int u)
{
ance[u] = u;
vis[u] = true;
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].fi;
if(vis[v]) continue;
dist[v] = dist[u] + G[u][i].se;
LCA(v);
unite(u, v);
ance[_find(u)] = u;
}
for(int i = head[u]; i != -1; i = query[i].next){
int v = query[i].to;
if(vis[v]) ans[query[i].index] = dist[u] + dist[v] - 2 * dist[ance[_find(u)]];
}
}
int main(void)
{
int u, v, k;
int a, b, c;
int Q;
int T;scanf("%d", &T);
while(T--){
init();
scanf("%d%d", &n, &Q);
for(int i = 0; i < n - 1; i++){
scanf("%d%d%d",&a, &b, &c);
G[a].push_back(p(b, c));
G[b].push_back(p(a, c));
}
for(int i = 0; i < Q; i++){
scanf("%d%d",&u,&v);
add_query(u, v, i);
}
dist[1] = 0;
LCA(1);
for(int i = 0; i <Q; i++){
printf("%d\n", ans[i]);
}
}
return 0;
}
E.Crossing River(数学)
题意:
经典过河问题,一个船只能运两个人,每个人过河时间不同,船来回往返使得
分析:
小学奥数题的感觉。。
决策如下:
1. n<=2 ,直接过河。
2. n=3 ,让最快的往返一次。
3. n>=4 ,要么最快的来回往返,要么最慢的和次慢的过去了就不要再回来。那么把最慢的和次慢的两个人送过去就有两种方法:
- 最小的来回往返,一次送最慢,回来,再送次慢。
- 最小和次小的一起送(要保证最慢的和次慢的过去以后还有人能把船运回来),最小的和次小的先一起过去,然后最小的回来,然后最慢的和次慢的一起过去,次小的回来。
取两种方法中的最小值即可。依次循环按上述策略处理。
代码:
/*************************************************************************
> File Name:
> Author: jiangyuzhu
> Mail: 834138558@qq.com
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
typedef pair<int, int>p;
typedef long long ll;
const int maxn = 1e3 + 5;
int a[maxn];
int main (void)
{
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i = 0; i < n; i++) cin>>a[i];
sort(a, a + n);
int ans = 0;
while(n){
if(n == 1){
ans += a[0];
break;
} else if(n == 2){
ans += a[1];
break;
}else if(n == 3){
ans += a[0] + a[1] + a[2];
break;
}else{
int t1 = a[0] * 2 + a[n - 1] + a[n - 2];
int t2 = a[1] * 2 + a[0] + a[n - 1];
n -= 2;
ans += min(t1, t2);
}
}
cout<<ans<<endl;
}
return 0;
}
G.Piotr’s Ants(找规律)
题意:
给定 n 个蚂蚁的初始位置及方向,两只蚂蚁相遇后同时掉头,速度均为
分析:
可以将问题看成,两只蚂蚁相遇后交换速度和方向,那么本质就相当于相遇后直接穿过对方,继续原方向行走,而由于两只蚂蚁相遇后直接掉头,所以最后 所有蚂蚁的相对顺序保持不变,这样我们算出所有终态,排个序,按照对应初始位置的顺序输出即可。注意判断是否掉下去的优先级要比位置是否相同的高!
代码:
/*************************************************************************
> Author: jiangyuzhu
> Mail: 834138558@qq.com
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 1e4 + 5, oo = 0x3f3f3f3f;
struct NODE{int x; int dir; int id;};
NODE node[maxn], nnode[maxn];
map<int, int>MAP;
bool cmp(NODE a, NODE b)
{
return a.x < b.x;
}
int main (void)
{
int N;sa(N);
for(int kas = 1; kas <= N; kas++){
int L, T, n;sa(L);sa(T);sa(n);
int x;
for(int i = 1; i <= n; i++){
sa(x);getchar();
if(getchar() == 'L'){
node[i] = (NODE){x, 1, i};
nnode[i] = (NODE){x - T, 1, 0};
}else{
node[i] = (NODE){x, 2, i};
nnode[i] = (NODE){x + T, 2, 0};
}
}
sort(node + 1, node + n + 1, cmp);
for(int i = 1; i <= n; i++){
MAP[node[i].id] = i;
}
sort(nnode + 1, nnode + n + 1, cmp);
for(int i = 1; i <= n; i++){
if(nnode[i].x < 0 || nnode[i].x > L)
nnode[i].dir = -1;
else if(i < n && nnode[i].x == nnode[i + 1].x){
nnode[i].dir = 0;
nnode[i + 1].dir = 0;
}
}
printf("Case #%d:\n", kas);
for(int j = 1; j <= n; j++){
int i = MAP[j];
if(nnode[i].dir == -1) {
cout<<"Fell off"<<endl;
continue;
}
cout<<nnode[i].x<<' ';
if(nnode[i].dir == 1){
cout<<'L'<<endl;
}else if(nnode[i].dir == 2){
cout<<'R'<<endl;
}else if(nnode[i].dir == 0){
cout<<"Turning"<<endl;
}
}
cout<<endl;
/*for(int i = 1; i <= n; i++){
cout<<nnode[i].id<<' '<<nnode[i].x<<' '<<nnode[i].dir<<endl;
}*/
}
return 0;
}
I.The Water Bowls(反转开关问题)
题意:
给定01序列,翻转一位其左右相邻两位也会被翻转,问使序列全部变为0的最小翻转次数。
分析:
简单线性排列,由于后面是否翻转由前面的决定,所以我们枚举第一个元素的状态,然后从头扫一遍即可。
代码:
/*************************************************************************
> Author: jiangyuzhu
> Mail: 834138558@qq.com
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 20 + 5, oo = 0x3f3f3f3f;
int f[maxn], a[maxn];
int main (void)
{
for(int i = 0; i < 20; i++){
cin>>a[i];
}
memset(f, 0, sizeof(f));
int cnt = 0;
for(int i = 1; i < 20; i++){
if((a[i - 1] + f[i - 1]) & 1){
cnt++;
f[i]++;
f[i + 1]++;
}
}
memset(f, 0, sizeof(f));
int cnt2 = 1;
f[0] = f[1] = 1;
for(int i = 1; i < 20; i++){
if((a[i - 1] + f[i - 1]) & 1){
cnt2++;
f[i]++;
f[i + 1]++;
}
}
cout<<min(cnt, cnt2)<<endl;
return 0;
}
J.Fliptile(反转开关问题)
题意:
有
分析:
一个块的转动会影响其他块的状态,这里不是简单的线性排列,不能只踩黑块。
首先根据字典序,我们可以对第一排从 00…00 到 11..11 进行考虑(1表示踩),再后续一排一排的考虑。因为踩一块四周的块都会转动,所以必须规定个踩的原则,发现对于某块来说,他一旦改变上方块的状态,那个块就再也不会改变了,而其他块还有他的下一列改变他的机会(如果存在),所以就以上一行块为原则,如果上方为黑,则踩。最后判断最后一行是否全白。
字典序,因为他说了是把整个排列当做字符串的字典序,所以肯定是越前面的越小越好,而且从第一个例子中也能看出来。
代码:
#include<iostream>
#include<cstring>
using namespace std;
#define mem(s,a) memset(s,a,sizeof(s));
int m, n;
const int maxn = 25, INF = 0x3fffffff;
int x[5]={-1,0,0,0,1};
int y[5] = {
0,1,0,-1,0};
int s[maxn][maxn], a[maxn][maxn], r[maxn][maxn], ans[maxn][maxn];
int cal()
{
int cnt = 0;
for(int i = 2; i <= m; i++){
for(int j = 1; j <= n; j++){
if((a[i-1][j] + r[i-1][j]) % 2 == 1) {
cnt++;
s[i][j] = 1;
}
for(int k = 0; k < 5; k++){
r[i + x[k]][j + y[k]] += s[i][j];
}
}
}
for(int i =1; i <= n; i++){
if((r[m][i] + a[m][i]) % 2 == 1) return -1;
}
return cnt;
}
int main (void)
{
cin>>m>>n;
for(int i = 1; i <= m; i++){
for(