Loading

团体程序设计天梯赛L3

L3-001 凑零钱

韩梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有 \(10^4\) 枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。

输入格式:

输入第一行给出两个正整数:\(N(≤10^4 )\)是硬币的总个数,\(M(≤10^2 )\)是韩梅梅要付的款额。

第二行给出 \(N\) 枚硬币的正整数面值。数字间以空格分隔。

输出格式:

在一行中输出硬币的面值 \(V_1≤V_2≤⋯≤V_k\),满足条件 \(V_1+V_2+…+V_k=M\)。数字间以 1 个空格分隔,行首尾不得有多余空格。若解不唯一,则输出最小序列。若无解,则输出 No Solution

注:我们说序列 \(\{ A[1],A[2],⋯ \}\)\(\{ B[1],B[2],⋯ \}\) “小”,是指存在 \(k≥1\) 使得 \(A[i]=B[i]\) 对所有 \(i<k\) 成立,并且 \(A[k]<B[k]\)

Solution

判断是否能够精准凑出要付的款额显然是个 01 背包问题,这个问题的困难在于输出的是最小序列。

由两条结论:

当凑一个款额时:

  • 硬币的数量越多序列越小。

  • 数量相同条件下,后面硬币面值越大,序列越小。

代码实现:

首先,我们可以从小到大对硬币进行排序,这样可以保证后面选的硬币会大,同时维护一个 \(num\) 数组,保证选择的硬币数量最少。输出的时候用 \(g\) 数组辅助输出就好。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 6;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = - 1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, m, tot;
int f[MAXN], a[MAXN], num[MAXN], g[MAXN], Ans[MAXN];
int main() {
    n = read(), m = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    f[0] = 1;
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i++) {
        for(int j = m; j >= a[i]; j--) {
            if(f[j] == 0 && f[j - a[i]] == 1) {
                f[j] = 1;
                g[j] = a[i];
                num[j] = num[j - a[i]] + 1;
            }
            else if(f[j] == 1 && f[j - a[i]] == 1) {
                if(num[j - a[i]] + 1 >= num[j]) {//注意思考这里为什么是大于等于而不是大于
                    num[j] = num[j - a[i]] + 1;
                    g[j] = a[i];
                }
            }
        }
    }
    if(f[m] == 0){ puts("No Solution"); return 0;}
    int now = m;
    while(now > 0) {
        Ans[++tot] = g[now];
        now -= g[now];
    }
    for(int i = tot; i >= 1; i--) {
        cout<<Ans[i];
        if(i != 1) cout<<" ";
    }
}


L3-002 特殊堆栈

堆栈是一种经典的后进先出的线性结构,相关的操作主要有“入栈”(在堆栈顶插入一个元素)和“出栈”(将栈顶元素返回并从堆栈中删除)。本题要求你实现另一个附加的操作:“取中值”——即返回所有堆栈中元素键值的中值。给定 \(N\) 个元素,如果 \(N\) 是偶数,则中值定义为第 \(N/2\) 小元;若是奇数,则为第 \((N+1)/2\) 小元。

输入格式:

输入的第一行是正整数 \(N(≤10^5)\)。随后 \(N\) 行,每行给出一句指令,为以下 3 种之一:

Push key
Pop
PeekMedian

其中 key 是不超过 \(10^5\) 的正整数;Push 表示“入栈”;Pop 表示“出栈”;PeekMedian 表示“取中值”。

输出格式:

对每个 Push 操作,将 key 插入堆栈,无需输出;对每个 PopPeekMedian 操作,在一行中输出相应的返回值。若操作非法,则对应输出 Invalid

Solution

该问题难点在于如何求中位数

四种方法

1、对顶堆

两个 multiset down 和 up,down 维护前 n / 2 大小的数,up 维护后 n / 2 大小的数,保证 down.size( ) = up.size( ) + 1 大小相等或down.size( ) = up.size( ) + 1, 中位数就是 *(down.end( ) - 1)

pushpop 操作可以用 stack 实现

注意时刻更新对顶堆大小就好了

Code

#include<bits/stdc++.h>
using namespace std;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int N;
char opt[20];
stack<int>stk;
multiset<int>up, down;
void update() {
    while(up.size() > down.size()) {
        auto it = up.begin();
        down.insert(*it);
        up.erase(it);
    }
    while(down.size() > up.size() + 1) {
        auto it = down.end();
        it--;
        up.insert(*it);
        down.erase(it);
    }
}
int main() {
    N = read();
    for(int i = 1; i <= N; i++) {
        scanf("%s", opt);
        if(strcmp(opt, "Pop") == 0) {
            if(stk.empty()) puts("Invalid");
            else {
                int x = stk.top(); stk.pop();
                auto it = down.end();
                it--;
                if(x <= *it) down.erase(down.find(x));
                else up.erase(up.find(x));
                cout<<x<<endl;
            }
            update();
        }
        else if(strcmp(opt, "Push") == 0) {
            int x = read(); stk.push(x);
            if(up.empty() || x < *up.begin()) down.insert(x);
            else up.insert(x);
            update();
        }
        else {
            if(stk.empty()) puts("Invalid");
            else {
                auto it = down.end();
                it--;
                cout<<*it<<endl;
            }
        }
    }
}

2、数状数组

发现数的值域并不大,考虑值域上的数状数组,中位数可以直接二分就好了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, t[N + 5];
string opt;
stack<int>stk;
void update(int x, int num) {
    for(int i = x; i <= N; i += i & (-i)) {
        t[i] += num;
    }
}
int query(int x) {
    int ret = 0;
    for(int i = x; i; i -= i & (-i)) {
        ret += t[i];
    }
    return ret;
}
int get_mid(int x) {
    if(x % 2 == 1) x += 1;
    x /= 2;
    int l = 1, r = N - 1;
    while(l < r) {
        int mid = l + r >> 1;
        if(query(mid) >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main() {
    n = read();
    for(int i = 1; i <= n; i++) {
        cin>>opt;
        if(opt == "Push") {
            int x = read();
            stk.push(x);
            update(x, 1);
        }
        else if(opt == "Pop") {
            if(stk.size() == 0) {puts("Invalid"); continue;}
            int x = stk.top(); stk.pop();
            cout<<x<<endl;
            update(x, -1);
        }
        else {
            if(stk.size() == 0) {puts("Invalid"); continue;}
            cout<<get_mid((int)stk.size())<<endl;
        }
    }
}

3、vector

利用 vector 也有自动排序的功能,直接二分就好了。

同时 vector 也支持 erase 操作,参数和 set 相同

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//Constant area
const int MAXN=1000000;
const int MIN_INF=0x80000000;
const int MAX_INF=0x7fffffff;
//Variable area
vector<int> v;
stack<int> s;
int n,tmp;
//Initialization area
void init(){ v.clear(); return; }
int main(){
    ios::sync_with_stdio(false);
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    init();
	cin>>n;
	while(n--){
		string ask;
		cin>>ask;
		if(ask[2]=='p'){
			if(v.size()==0){
				cout<<"Invalid"<<endl;
				continue;
			}
			auto i=lower_bound(v.begin(),v.end(),s.top());
			cout<<*i<<endl;
			v.erase(i);
			s.pop();
		}else if(ask[2]=='e'){
			if(v.size()==0){
				cout<<"Invalid"<<endl;
				continue;
			}
			int mid=v.size()%2? v.size()/2:v.size()/2-1;
			cout<<v[mid]<<endl;
		}else if(ask[2]=='s'){
			cin>>tmp;
			auto i=lower_bound(v.begin(),v.end(),tmp);
			v.insert(i,tmp);
			s.push(tmp);
		}
	}
}

4、线段树

利用线段数查找区间第 k 大

值域线段数

Code

    #include<bits/stdc++.h>

using namespace std;
#define lowbit(x) x&-x
const int N = 1e5 + 10;

int s[N], top, pre;
int t[N << 2];

void update(int l, int r, int x, int num, int pos)
{
	if(l == r)
	{
		t[pos] += num;
		return;
	}
	int mid = l + r >> 1;
	if(mid >= x) update(l, mid, x, num, pos<<1);
	else update(mid + 1, r, x, num, pos<<1|1);
	t[pos] = t[pos<<1] + t[pos<<1|1];
}

int query(int l, int r, int y, int pos)
{
	if(l == r)
		return l;
	int mid = l + r >> 1;
	if(y <= t[pos<<1])
		return query(l, mid, y, pos<<1);
	else
		return query(mid + 1, r, y - t[pos<<1], pos<<1|1);
}
int main()
{
	int t;
	cin >> t;
	pre = top = 1;
	while(t --)
	{
		string s1;
		cin >> s1;
		if(s1 == "Pop")
		{
			if(top == 1)
				cout << "Invalid" << endl;
			else
			{
				top --;
				update(1, N - 1, s[top], -1, 1);
				cout << s[top] << endl;
			}
		}
		else if(s1 == "PeekMedian")
		{
			if(top == 1)
			{
				cout << "Invalid" << endl;
			}
			else
			{
				int x = top;
				x -= 1;
				if(x % 2 == 1)	x += 1;
				x /= 2;
				cout << query(1, N - 1, x, 1) << endl;
			}
		}
		else
		{
			int x;
			cin >> x;
			s[top ++] = x;
			update(1, N - 1, x, 1, 1);
		}
	}
	return 0;
}


L3-003 社交集群

当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。

输入格式:

输入在第一行给出一个正整数 \(N(≤1000)\),为社交网络平台注册的所有用户的人数。于是这些人从 \(1\)\(N\) 编号。随后 \(N\) 行,每行按以下格式给出一个人的兴趣爱好列表:

\(K_i​: h_i​[1] h_i[2] ... h_i[K_i]\)
其中 \(K_i(>0)\) 是兴趣爱好的个数,\(h_i[j]\) 是第 \(j\) 个兴趣爱好的编号,为区间 \([1, 1000]\) 内的整数。

输出格式:

首先在一行中输出不同的社交集群的个数。随后第二行按非增序输出每个集群中的人数。数字间以一个空格分隔,行末不得有多余空格。

Solution

并查集,没有啥好说。。。

判断两个人是否有共同爱好方法很多,这用 set 处理的

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, fa[MAXN], ans[MAXN], Ans[MAXN], tot;
char c;
set<int>st[MAXN];
int Find(int x){
    if(fa[x] == x) return x;
    return fa[x] = Find(fa[x]);
}
bool cmp(int x, int y) {
    return x > y;
}
int main(){
    n = read();
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= n; i++) {
        int k = read();
        c = getchar();
        for(int j = 1; j <= k; j++)  {
            int x = read();
            st[i].insert(x);
        }
    }
    for(int i = 1; i <= n; i++) {
        for(auto j:st[i]) {
            for(int w = i + 1; w <= n; w++) {
                if(st[w].find(j) != st[w].end()) {
                    if(Find(i) != Find(w)) {
                        if(Find(i) < Find(w)) fa[Find(i)] = Find(w);
                        else fa[Find(w)] = Find(i);
                    }
                    
                }
            }
        }
    }
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(Find(fa[i]) == i) cnt++;
        ans[Find(fa[i])]++;
    }
    for(int i = 1; i <= n; i++) {
        if(ans[i] != 0) Ans[++tot] = ans[i];
    }
    sort(Ans + 1, Ans + tot + 1, cmp);
    cout<<cnt<<endl;
    for(int i = 1; i <= tot; i++) {
        cout<<Ans[i];
        if(i != tot) cout<<" ";
    }
}

L3-004 肿瘤诊断

在诊断肿瘤疾病时,计算肿瘤体积是很重要的一环。给定病灶扫描切片中标注出的疑似肿瘤区域,请你计算肿瘤的体积。

输入格式:

输入第一行给出 4 个正整数:\(M\)\(N\)\(L\)\(T\),其中M和N是每张切片的尺寸(即每张切片是一个 \(M×N\) 的像素矩阵。最大分辨率是 \(1286\times128\) );\(L(\leq 60)\)是切片的张数;T是一个整数阈值(若疑似肿瘤的连通体体积小于T,则该小块忽略不计)。

最后给出 \(L\) 张切片。每张用一个由 0 和 1 组成的 \(M\times N\)的矩阵表示,其中 1 表示疑似肿瘤的像素,0 表示正常像素。由于切片厚度可以认为是一个常数,于是我们只要数连通体中1的个数就可以得到体积了。麻烦的是,可能存在多个肿瘤,这时我们只统计那些体积不小于 \(T\) 的。两个像素被认为是“连通的”,如果它们有一个共同的切面,如下图所示,所有 6 个红色的像素都与蓝色的像素连通。

输出格式:

在一行中输出肿瘤的总体积。

Solution

三维的连通块问题,直接 BFS

简单了说就是BFS遍历三位度的图,比二维图添加了两个方向而已。

注意的是统计答案不要在入队的时候统计,因为会重复统计。

因为一个点可能会被多次入队

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int dx[6] = {0, 0, 1, -1, 0, 0};
const int dy[6] = {1, -1, 0, 0, 0, 0};
const int dz[6] = {0, 0, 0, 0, 1, -1};
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int M, N, L, T, Ans;
int mp[1300][140][70], vis[1300][140][70];
struct node{int x, y, z;};
bool Check(int x, int y, int z) {
    if(x < 1 || x > M || y < 1 || y > N || z < 1 || z > L) return false;
    if(vis[x][y][z] || mp[x][y][z] == 0) return false;
    return true;
}
queue<node>q;
int Bfs(int x, int y, int z) {
    int tmp = 0;
    q.push((node){x, y, z});
    while(!q.empty()) {
        node u = q.front(); q.pop();
        int nx = u.x, ny = u.y, nz = u.z;
        if(vis[nx][ny][nz]) continue;
        vis[nx][ny][nz] = 1;
        tmp++;
        for(int i = 0; i < 6; i++) {
            int tx = nx + dx[i], ty = ny + dy[i], tz = nz + dz[i];
            if(Check(tx, ty, tz)) {
                q.push((node){tx, ty, tz});
            }
        }
    }
    return tmp;
}
int main(){
    M = read(), N = read(), L = read(), T = read();
    for(int k = 1; k <= L; k++)
        for(int i = 1; i <= M; i++)
            for(int j = 1; j <= N; j++) mp[i][j][k] = read();
    for(int k = 1; k <= L; k++){
        for(int i = 1; i <= M; i++){
            for(int j = 1; j <= N; j++) {
                if(!mp[i][j][k] || vis[i][j][k]) continue;
                int tnum = Bfs(i, j, k);
                if(tnum >= T) Ans += tnum;
            }
        }
    }
    cout<<Ans;
}

L3-005 垃圾箱分布

大家倒垃圾的时候,都希望垃圾箱距离自己比较近,但是谁都不愿意守着垃圾箱住。所以垃圾箱的位置必须选在到所有居民点的最短距离最长的地方,同时还要保证每个居民点都在距离它一个不太远的范围内。

现给定一个居民区的地图,以及若干垃圾箱的候选地点,请你推荐最合适的地点。如果解不唯一,则输出到所有居民点的平均距离最短的那个解。如果这样的解还是不唯一,则输出编号最小的地点。

输入格式:

输入第一行给出4个正整数:\(N(≤10^3)\) 是居民点的个数; \(M(\leq 10)\) 是垃圾箱候选地点的个数; \(K(≤10^4)\)是居民点和垃圾箱候选地点之间的道路的条数; \(D_S\) 是居民点与垃圾箱之间不能超过的最大距离。所有的居民点从 \(1\)\(N\) 编号, 所有的垃圾箱候选地点从 \(G1\)\(GM\) 编号。

随后 \(K\) 行, 每行按下列格式描述一条道路:

P1 P2 Dist

其中 P1P2 是道路两端点的编号, 端点可以是居民点, 也可以是垃圾箱候选点。Dist 是道路的长度,是一个正整数。

输出格式:

首先在第一行输出最佳候选地点的编号。然后在第二行输出该地点到所有居民点的最小距离和平均距离。数字间以空格分隔,保留小数点后 1 位。如果解不存在,则输出 No Solution

Solution

跑单元最短路就好 (Dijkstra)

Dijkstra 堆优化的时间复杂度是 \(O((V + E)logE)\)

写的时候出了点小问题: 用 printf 进行四舍五入可能会出现精度问题,用 round 可以处理这一情况

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int N, M, K, D, dis[MAXN], vis[MAXN], tot;
struct edge{int v, w, nxt;}e[MAXN << 1];
struct node{
    int id, w;
    bool operator < (const node &rhs) const {
        return w > rhs.w;
    }
};
struct Node{
    double avg;
    int Min, id;
}a[MAXN];
bool cmp(Node x, Node y) {
    if(x.avg == y.avg && x.Min == y.Min) return x.id < y.id;
    else if(x.Min == y.Min) return x.avg < y.avg;
    return x.Min > y.Min;
}
int E, head[MAXN];
void add(int u, int v, int w) {
    e[++E] = (edge){v, w, head[u]};
    head[u] = E;
}
priority_queue<node>q;
void Dijkstra(int x) {
    memset(dis, 0x3f3f, sizeof dis);
    memset(vis, 0, sizeof vis);
    dis[x] = 0;
    q.push((node){x, 0});
    while(!q.empty()) {
        int u = q.top().id; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                q.push((node){v, dis[v]});
            }
        }
    }
}
int main(){
    N = read(), M = read(), K = read(), D = read();
    for(int i = 1; i <= K; i++) {
        string x, y;
        int u, v, w;
        cin>>x>>y>>w;
        if(x[0] == 'G') {
            x = x.substr(1);
            u = N + stoi(x);
        }
        else u = stoi(x);
        if(y[0] == 'G') {
            y = y.substr(1);
            v = N + stoi(y);
        }
        else v = stoi(y);
        add(u, v, w), add(v, u, w);
    }
    for(int i = N + 1; i <= N + M; i++) {
        Dijkstra(i);
        int fag = 1, Min = 0x3f3f3f3f;
        double avg = 0;
        for(int j = 1; j <= N; j++) {
            Min = min(Min, dis[j]);
            if(dis[j] > D) {fag = 0; break;}
            avg += dis[j];
        }
        
        if(!fag) continue;
         a[++tot].avg = avg * 1.000 / N;
         a[tot].id = i;
         a[tot].Min = Min;
    }
    if(tot == 0) puts("No Solution");
    else {
        sort(a + 1, a + tot + 1, cmp);
        if(a[1].id > N) cout<<"G"<<a[1].id - N<<endl;
        else cout<<a[1].id<<endl;
        printf("%.1lf %.1lf", (double)a[1].Min, round(a[1].avg * 10) / 10);
    }
}

L3-006 迎风一刀斩

迎着一面矩形的大旗一刀斩下,如果你的刀够快的话,这笔直一刀可以切出两块多边形的残片。反过来说,如果有人拿着两块残片来吹牛,说这是自己迎风一刀斩落的,你能检查一下这是不是真的吗?
注意摆在你面前的两个多边形可不一定是端端正正摆好的,它们可能被平移、被旋转(逆时针90度、180度、或270度),或者被(镜像)翻面。

这里假设原始大旗的四边都与坐标轴是平行的。

输入格式:

输入第一行给出一个正整数 \(N(\leq 20)\) ,随后给出N对多边形。每个多边形按下列格式给出:

\( k~~~ x_1~~ y_1 \dots x_k~~ y_k \)

其中 \(k(2<k≤10)\) 是多边形顶点个数;\((x_i,y_i)\) \((0 \leq x_i, y_i \leq 10^8)\) 是顶点坐标,按照顺时针或逆时针的顺序给出。

注意:题目保证没有多余顶点。即每个多边形的顶点都是不重复的,任意3个相邻顶点不共线。

输出格式:

对每一对多边形,输出 YES 或者 NO

Solution

一道很好的平面几何规律题。

首先题目保证首先大旗各边是平行坐标轴的,切完之后也是平行于坐标轴的,这个性质比较重要。

我们可以按照切完后两个多边形的总边数和平行于坐标轴的边数可以分为 5 种情况, 如下图所示:

对于第四种情况,只需要判断是否有一条边相等即可。

对于第五种情况,判断一下两多边形斜边以及宽是否相等即可。

对于前三种情况,所得到的都是一个三角形 + 一个多边形,并且多边形都满足平行于坐标轴的边数为总边数 -1 的性质,利用这个性质可以 pass 掉一些不可法的多边形,剩下的利用平行于坐标轴的边数长度判断两个图形是否能拼起来就好。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
vector<pair<int, int>> A(10), B(10);
int n, k1, k2, SLength, LLength, SWidth, LWidth, res, dif, D1, D2;
//SLength 为较短的平行于 x 轴的直角边长度, LLength 为较长的平行于 x 轴的直角边长度
//SWidth 为较短的平行于 y 轴的直角边长度, LWidth 为较长的平行于 y 轴的直角边长度
//res 有多少条直角边,dif 表示边长
void Deal(const vector<pair<int, int>> &C, const int &l) {
    res = SLength = LLength = SWidth = LWidth = 0;
    for(int i = 0; i < l; i++) {
        if(C[i].first == C[(i + 1) % l].first) {
            res++;
            dif = abs(C[i].second - C[(i + 1) % l].second);//边长
            if(dif > LLength) SLength = LLength, LLength = dif;
            else SLength = dif;
        }else if(C[i].second == C[(i + 1) % l].second){
            res++;
            dif = abs(C[i].first - C[(i + 1) % l].first);
            if(dif > LWidth) SWidth = LWidth, LWidth = dif;
            else SWidth = dif;
        }
    }
}
string judge() {
    if(k1 > 5 || k2 > 5) return "NO";//不可能切出五边形
    if(k1 == 4 && k2 == 4) {//都是四边形
        Deal(A, k1);
        if(res == 4) { //两个矩形的情况
            D1 = LLength, D2 = LWidth;
            Deal(B, k2);
            if(res != 4) return "NO";
            if(D1 == LWidth || D1 == LLength || D2 == LWidth || D2 == LLength) return "YES";
        }
        else if(res == 3) {//两个直角梯形
            if(SWidth == 0) D1 = LWidth, D2 = LLength - SLength;
            else D1 = LLength, D2 = LWidth - SWidth;
            Deal(B, k2);
            if(res != 3) return "NO";
            if(SWidth == 0 && D1 == LWidth && D2 == LLength - SLength) return "YES";
            else if(SLength == 0 && D1 == LLength && D2 == LWidth - SWidth) return  "YES";
        }
        return "NO";
    }
    if(k2 > k1) swap(k1, k2), swap(A, B);
    Deal(A, k1);
    if(res != k1 - 1) return "NO";
    D1 = LLength - SLength, D2 = LWidth - SWidth;
    Deal(B, k2);
    if(res != k2 - 1) return "NO";
    if((D1 == LLength && D2 == LWidth) || (D1 == LWidth && D2 == LLength)) return "YES";
    return "NO";
}
int main(){
    n = read();
    while(n--) {
        k1 = read();
        for(int i = 0; i < k1; i++) A[i].first = read(), A[i].second = read();
        k2 = read();
        for(int i = 0; i < k2; i++) B[i].first = read(), B[i].second = read();
        cout<<judge()<<"\n";
    }
}

L3-007 天梯地图

本题要求你实现一个天梯赛专属在线地图,队员输入自己学校所在地和赛场地点后,该地图应该推荐两条路线:一条是最快到达路线;一条是最短距离的路线。题目保证对任意的查询请求,地图上都至少存在一条可达路线。

输入格式:

输入在第一行给出两个正整数 N\((2 \leq N \leq 500)\)M,分别为地图中所有标记地点的个数和连接地点的道路条数。随后 M 行,每行按如下格式给出一条道路的信息:

V1 V2 one-way length time

其中 V1V2 是道路的两个端点的编号(从 0N-1);如果该道路是从 V1V2 的单行线,则 one-way 为 1,否则为 0;length 是道路的长度;time 是通过该路所需要的时间。最后给出一对起点和终点的编号。

输出格式:

首先按下列格式输出最快到达的时间T和用节点编号表示的路线:

Time = T: 起点 => 节点1 => ... => 终点

然后在下一行按下列格式输出最短距离D和用节点编号表示的路线:

Distance = D: 起点 => 节点1 => ... => 终点

如果最快到达路线不唯一,则输出几条最快路线中最短的那条,题目保证这条路线是唯一的。而如果最短距离的路线不唯一,则输出途径节点数最少的那条,题目保证这条路线是唯一的。

如果这两条路线是完全一样的,则按下列格式输出:

Time = T; Distance = D: 起点 => 节点1 => ... => 终点

Solution

这道题偷懒了 QwQ

两边 Dijkstra 就好了,根据最短时间和距离以及最小经过的节点数记录下每个点的前驱,然后用前驱打印路径就好了。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, m, con[MAXN], con1[MAXN];
struct edge{int v, nxt, t, w;}e[MAXN << 1];
int E, head[MAXN];
void add(int u, int v, int t, int w) {
    e[++E] = (edge){v, head[u], t, w};
    head[u] = E;
}
int pre[MAXN], dis[MAXN], dis1[MAXN], Min[MAXN], cnt[MAXN], vis[MAXN], pre1[MAXN];
struct Node{
    int id, w;
    bool operator < (const Node &rhs)const {
        return w > rhs.w;
    }
};

void dijkstra(int s) {
    memset(dis, 0x3f3f3f, sizeof dis);
    memset(vis, 0, sizeof vis);
    priority_queue<Node>q;
    q.push((Node){s, 0});
    dis[s] = 0;
    while(!q.empty()) {
        int u = q.top().id; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(dis[u] + e[i].t < dis[v]) {
                dis[v] = dis[u] + e[i].t;
                q.push((Node){v, dis[v]});
                Min[v] = Min[u] + e[i].w;
                pre[v] = u;
            }
            if(dis[u] + e[i].t == dis[v]) {
                if(Min[v] > Min[u] + e[i].w) {
                    Min[v] = Min[u] + e[i].w;
                    pre[v] = u;
                }
            }
        }
    }
}

void Dijkstra(int s){
    memset(dis1, 0x3f3f3f, sizeof dis1);
    memset(vis, 0, sizeof vis);
    priority_queue<Node>q;
    q.push((Node){s, 0});
    dis1[s] = 0;
    while(!q.empty()){
        int u = q.top().id; q.pop();
        if(vis[u])continue;
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt){
            int v = e[i].v;
            if(dis1[u] + e[i].w == dis1[v]){//核心代码
                if(cnt[v] > cnt[u]+1){
                    pre1[v] = u;
                }
            }//核心代码
            if(dis1[u] + e[i].w < dis1[v]){
                dis1[v] = dis1[u] + e[i].w;
                q.push((Node){v, dis1[v]});
                cnt[v] = cnt[u] + 1;
                pre1[v] = u;
            }
        }
    }
}

void print1(int x){
    if(pre1[x] == 0){
        cout << x ;
        return;
    }
    print1(pre1[x]);
    cout <<" => "<< x;
}
 
void print(int x){
    if(pre[x] == 0){
        cout << x ;
        return;
    }
    print(pre[x]);
    cout <<" => "<< x;
}
int main(){
    n = read(), m = read();
    for(int i = 1; i <= m; i++) {
        int a = read(), b = read(), c = read(), d = read(), e = read();
        if(c) {
            add(a, b, e, d);
        }
        else {
            add(a, b, e, d), add(b, a, e, d);
        }
    }
    int s = read(), end = read();
    dijkstra(s), Dijkstra(s);
    int Idx = 1, Idx1 = 1;
    
    for(int i = pre[end]; i; i = pre[i]) con[Idx++] = i;
    for(int i = pre1[end]; i; i = pre1[i]) con1[Idx1++] = i;
    int t = 0, tag = max(Idx, Idx1);
    for(int i = 1; i <= tag; i++){
        if(con[i] != con1[i]) t = 1;
    }
    if(t == 0){
        cout << "Time = "<< dis[end] << "; Distance = "<<dis1[end] << ": ";
        print(end);
        return 0;
    }
    cout << "Time = "<<dis[end]<<": ";
    print(end);
    cout <<endl;
    cout << "Distance = "<<dis1[end]<<": ";
    print1(end);
}

L3-008 喊山

给出一张点数为 \(n\) ,边数为 \(m\) 的无向图,\(k\) 次询问给出 \(k\) 个点, 依此输出图中到每个点距离最远的点是哪个,若有多个输出编号最小的一个。

输入格式:

输入第一行给出 3 个正整数 nmk,其中 n\(\leq 10000\))是总的山头数(于是假设每个山头从 1n 编号)。接下来的 m 行,每行给出 2 个不超过 n 的正整数,数字间用空格分开,分别代表可以听到彼此的两个山头的编号。这里保证每一对山头只被输入一次,不会有重复的关系输入。最后一行给出\(k(\leq 10)\) 个不超过 n 的正整数,数字间用空格分开,代表需要查询的山头的编号。

输出格式:

依次对于输入中的每个被查询的山头,在一行中输出其发出的呼喊能够连锁传达到的最远的那个山头。注意:被输出的首先必须是被查询的个山头能连锁传到的。若这样的山头不只一个,则输出编号最小的那个。若此山头的呼喊无法传到任何其他山头,则输出 0。

Solution

没什么好说的,直接 BFS 就好

想到了树上求最长链问题。。。。。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, m, k, dis[MAXN], vis[MAXN], Maxdis, Min;
struct edge{int v, nxt;}e[MAXN << 1];
int E, head[MAXN];
void add(int u, int v) {
    e[++E] = (edge){v, head[u]};
    head[u] = E;
}
void Bfs(int s) {
    queue<pair<int, int> >q;
    memset(dis, 0, sizeof dis);
    memset(vis, 0, sizeof vis);
    q.push({s, 0});
    vis[s] = 1;
    while(!q.empty()) {
        int fir = q.front().first, sec = q.front().second; q.pop();
        for(int i = head[fir]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(vis[v]) continue;
            vis[v] = 1;
            if(sec + 1 > dis[v]) {
                dis[v] = sec + 1;
                if(Maxdis < dis[v]) {
                    dis[v] = Maxdis;
                    Min = v;
                }
            }
            if(sec + 1 == dis[v]) {
                Min = min(v, Min);
            }
            q.push({v, dis[v]});
        }
    }
}
int main(){
    n = read(), m = read(), k = read();
    for(int i = 1; i <= m; i++) {
        int u = read(), v = read();
        add(u, v), add(v, u);
    }
    for(int i = 1; i <= k; i++) {
        Maxdis = 0;
        Min = 0;
        int x = read();
        Bfs(x);
        cout<<Min<<endl;
    }
}

L3-009 长城

题面给个链接吧,有点长 =_=!

题面

Solution

平面几何题(薄弱点)

发现只有凸点才需要建烽火台,判断凸点用斜率就好了。

避免精度问题可以转为乘法。

被 long long 卡了

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, x[MAXN], y[MAXN], st[MAXN], tp;
set<int>s;
bool Check(int a, int b, int c) {
    return (y[b] - y[a]) * (x[c] - x[a])  <=  (y[c] - y[a]) * (x[b] - x[a]);
}
signed main(){
    n = read();
    for(int i = 1; i <= n; i++) {
        x[i] = read(), y[i] = read();
        while(tp >= 2 && Check(i, st[tp], st[tp - 1])) tp--;
        if(tp >= 2) s.insert(st[tp]);
        st[++tp] = i;
    }
    cout<<s.size()<<endl;
}

L3-010 是否完全二叉搜索树

将一系列给定数字顺序插入一个初始为空的二叉搜索树(定义为左子树键值大,右子树键值小),你需要判断最后的树是否一棵完全二叉树,并且给出其层序遍历的结果。

输入格式:

输入第一行给出一个不超过 20 的正整数 N;第二行给出 N 个互不相同的正整数,其间以空格分隔。

输出格式:

将输入的 N 个正整数顺序插入一个初始为空的二叉搜索树。在第一行中输出结果树的层序遍历结果,数字间以 1 个空格分隔,行的首尾不得有多余空格。第二行输出YES,如果该树是完全二叉树;否则输出 NO

Solution

递归建树,然后用队列输出层序遍历。

判断是否为完全二叉树:保证前 n 个节点都不为空节点就好了

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read() {
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, t[MAXN], k;
void build(int pos, int val) {
    if(t[pos] == -1) {
        t[pos] = val;
        t[pos * 2] = t[pos * 2 + 1] = -1;
        return ;
    }
    if(val >= t[pos]) build(pos * 2, val);
    else build(pos * 2 + 1, val);
}
queue<int>q;
int main(){
    n = read();
    t[1] = -1;
    for(int i = 1; i <= n; i++) {
        int x = read();
        build(1, x);
    }
    int fag = 0;
    q.push(1);
    while(!q.empty()) {
        int u = q.front(); q.pop();
        if(k == 0) printf("%d", t[u]), k = 1;
        else printf(" %d", t[u]);
        if(t[u * 2] == -1) {
            if(u * 2 <= n) fag = 1;
        }
        else q.push(u * 2);
        if(t[u * 2 + 1] == -1) {
            if(u * 2 + 1 <= n) fag = 1;
        }
        else q.push(u * 2 + 1);
    }
    puts("");
    if(fag) puts("NO");
    else puts("YES");
}

L3-011 直捣黄龙

本题是一部战争大片 —— 你需要从己方大本营出发,一路攻城略地杀到敌方大本营。首先时间就是生命,所以你必须选择合适的路径,以最快的速度占领敌方大本营。当这样的路径不唯一时,要求选择可以沿途解放最多城镇的路径。若这样的路径也不唯一,则选择可以有效杀伤最多敌军的路径。

输入格式:

输入第一行给出 2 个正整数 \(N(2 \leq N \leq 200\),城镇总数)和 \(K\)(城镇间道路条数),以及己方大本营和敌方大本营的代号。随后 \(N-1\) 行,每行给出除了己方大本营外的一个城镇的代号和驻守的敌军数量,其间以空格分隔。再后面有 \(K\) 行,每行按格式城镇1 城镇2 距离给出两个城镇之间道路的长度。这里设每个城镇(包括双方大本营)的代号是由 3 个大写英文字母组成的字符串。

输出格式:

按照题目要求找到最合适的进攻路径(题目保证速度最快、解放最多、杀伤最强的路径是唯一的),并在第一行按照格式己方大本营->城镇1->...->敌方大本营 输出。第二行顺序输出最快进攻路径的条数、最短进攻距离、歼敌总数,其间以 1 个空格分隔,行首尾不得有多余空格。

Solution

最短路 + 路径记录

松弛的时候多维护一下走的点数和歼敌总数就好了。

写的时候记录路径总数的时候写错了(一开始只在终点统计的,这会漏掉很多情况)

偷什么懒,就得老实 dp!!! =-=!

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, k, eny[MAXN], tot, dis[MAXN], vis[MAXN], num[MAXN], all[MAXN];
int pre[MAXN], ans[MAXN], anstot, road[MAXN];
string st, ed, id[MAXN];
struct edge{int v, w, nxt;}e[MAXN << 1];
int E, head[MAXN];
void add(int u, int v, int w) {
    e[++E] = (edge) {v, w, head[u]};
    head[u] = E;
}
map<string, int> mp;
struct node{
    int id, w;
    bool operator < (const node &rhs) const {
        return w > rhs.w;
    }
};
priority_queue<node>q;
void Dijkstra(int x) {
    memset(dis, 0x3f3f3f3f, sizeof dis);
    dis[x] = 0;
    q.push((node){x, 0});
    road[x] = 1;
    while(!q.empty()) {
        int u = q.top().id; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(dis[v] > dis[u] + e[i].w) {
                pre[v] = u;
                dis[v] = dis[u] + e[i].w;
                num[v] = num[u] + 1;
                all[v] = all[u] + eny[v];
                q.push((node){v, dis[v]});
                road[v] = road[u];
            }
            else if(dis[v] == dis[u] + e[i].w) {
                if(num[v] < num[u] + 1) {
                    num[v] = num[u] + 1;
                    all[v] = all[u] + eny[v];
                    pre[v] = u;
                }
                else if(num[v] == num[u] + 1) {
                    if(all[v] < all[u] + eny[v]) {
                        all[v] = all[u] + eny[v];
                        pre[v] = u;
                    }
                }
                road[v] += road[u];
            }
        }
    }
}
int main(int argc, const char * argv[]) {
    n = read(), k = read();
    cin>>st>>ed;
    if(mp[st] == 0) mp[st] = ++tot, id[tot] = st;
    
    if(mp[ed] == 0) mp[ed] = ++tot, id[tot] = ed;
    for(int i = 1; i < n; i++) {
        string x;
        cin>>x;
        if(!mp[x]) mp[x] = ++tot, id[tot] = x;
        eny[mp[x]] = read();
    }
    
    for(int i = 1; i <= k; i++) {
        string x, y;
        int w;
        cin>>x>>y>>w;
        if(!mp[x]) mp[x] = ++tot, id[tot] = x;
        if(!mp[y]) mp[y] = ++tot, id[tot] = y;
        add(mp[x], mp[y], w), add(mp[y], mp[x], w);
    }
    Dijkstra(mp[st]);
    int now = mp[ed];
    while(now != mp[st]) {
        ans[++anstot] = now;
        now = pre[now];
    }
    ans[++anstot] = mp[st];
    for(int i = anstot; i >= 1; i--) {
        if(i != anstot) cout<<"->"<<id[ans[i]];
        else cout<<id[ans[i]];
    }
    puts("");
    cout<<road[mp[ed]]<<" "<<dis[mp[ed]]<<" "<<all[mp[ed]];
    return 0;
}

L3-012 水果忍者

题面有点长,就不粘了,点这题面

Solution

首先,这个题还是蛮有意思滴。

然后呢,我没想到咋解 /kk

说正事,说正事...

这个题保证是有解的,而且发现题面中有个很重要的性质:一定存在四个坐标全是整数的解。

然后呢,我们很容易想到这么一个性质:

如果存在一个解,我们把这条直线向上平移,移到极限情况下,一定会经过某条线段的上端点,然后我们把这条直线绕着这个点旋转,一定可以使它经过其他“水果”的上端点或者下端点,这样我们通过旋转操作对于每个“其他水果”可以求出该直线一个斜率的取值范围;

最后判断所有这些斜率的取值范围是否有交集,若有交点说明最开始上端点是合法的,若没有说明最开始的上端点不合法,继续枚举其他水果的上端点。

没有图比较难理解,代码非常通俗易懂哈

复杂度很显然就是 \(O(n^2)\)

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int N;
struct Node{
    int x, y1, y2;
    bool operator < (const Node &rhs) const {
        return x < rhs.x;
    }
}Points[MAXN];
int main(int argc, const char * argv[]) {
    N = read();
    for(int i = 1; i <= N; i++) {
        Points[i].x = read();
        Points[i].y1 = read(), Points[i].y2 = read();
    }
    sort(Points + 1, Points + N + 1);
    for(int i = 1; i <= N; i++) {
        bool fag = true;
        double kmax = INF, kmin = -INF;
        double tmax, tmin;
        int x, y;
        for(int j = 1; j <= N; j++) {
            if(j == i) continue;
            if(i < j) {
                tmax = (double) (Points[j].y1 - Points[i].y1) / (Points[j].x - Points[i].x);
                tmin = (double) (Points[j].y2 - Points[i].y1) / (Points[j].x - Points[i].x);
            }
            else {
                tmax = (double) (Points[i].y1 - Points[j].y2) / (Points[i].x - Points[j].x);
                tmin = (double) (Points[i].y1 - Points[j].y1) / (Points[i].x - Points[j].x);
            }
            if(tmax < kmax) kmax = tmax, x = Points[j].x, y = Points[j].y1;
            if(tmin > kmin) kmin = tmin, x = Points[j].x, y = Points[j].y2;
            if(kmax < kmin) {
                fag = false;
                break;
            }
        }
        if(fag) {
            cout<<Points[i].x << " "<< Points[i].y1 << " "<<x <<" "<<y<<endl;
            break;
        }
    }
    return 0;
}

L3-013 非常弹的球##

刚上高一的森森为了学好物理,买了一个“非常弹”的球。虽然说是非常弹的球,其实也就是一般的弹力球而已。森森玩了一会儿弹力球后突然想到,假如他在地上用力弹球,球最远能弹到多远去呢?他不太会,你能帮他解决吗?当然为了刚学习物理的森森,我们对环境做一些简化:

  • 假设森森是一个质点,以森森为原点设立坐标轴,则森森位于 (0, 0) 点。

  • 小球质量为 \(w/100\) 千克(kg),重力加速度为9.8米/秒平方(\(m/s^2\))。

  • 森森在地上用力弹球的过程可简化为球从(0, 0)点以某个森森选择的角度 \(ang\) \((0<ang<π/2)\) 向第一象限抛出,抛出时假设动能为1000 焦耳(J)。

  • 小球在空中仅受重力作用,球纵坐标为 0 时可视作落地,落地时损失 \(p\%\) 动能并反弹。

  • 地面可视为刚体,忽略小球形状、空气阻力及摩擦阻力等。

森森为你准备的公式:

动能公式:\(E=\frac{1}{2}mv^2\)

牛顿力学公式:\(F=m\times a\)

重力:\(G = m\times g\)

其中:

  • \(E\) - 动能,单位为“焦耳”
  • \(m\) - 质量,单位为“千克”
  • \(v\) - 速度,单位为“米/秒”
  • \(a\) - 加速度,单位为“米/秒平方”
  • \(g\) - 重力加速度

输入格式:

输入在一行中给出两个整数:\(1≤w≤1000\)\(1≤p≤100\),分别表示放大100倍的小球质量、以及损失动力的百分比 \(p\)

输出格式:

在一行输出最远的投掷距离,保留 3 位小数。

Solution

水平速度:

\[v_x = v\cos{\theta} \]

竖直速度:

\[v_y = v\sin{\theta} \]

水平距离:

\[S_x = \frac{2v^2\cos{\theta}\sin{\theta}}{g} = \frac{v^2\sin{2\theta}}{g} \]

\(\theta = \frac{\pi}{4}\) 时,\(S_x\) 取最大值。

所以抛出的角度应该为 $ \frac{\pi}{4}$。

因为后面每次反弹整体的动能缩小 \(p\%\) ,也是就 \(v^2\) 缩小 \(p\%\) ,所以只需要保证每次跳的最远,总距离就最远。

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
double m, p;
int main(int argc, const char * argv[]) {
    cin>>m>>p;
    m /= 100, p /= 100;
    double v = 2000 / m; //这是v^2
    double ans = 0;
    while(v > 1e-9) {
        ans += v / 9.8;
        v *= (1 - p);
    }
    printf("%.3lf",ans);
    return 0;
}

L3-014 周游世界

周游世界是件浪漫事,但规划旅行路线就不一定了…… 全世界有成千上万条航线、铁路线、大巴线,令人眼花缭乱。所以旅行社会选择部分运输公司组成联盟,每家公司提供一条线路,然后帮助客户规划由联盟内企业支持的旅行路线。本题就要求你帮旅行社实现一个自动规划路线的程序,使得对任何给定的起点和终点,可以找出最顺畅的路线。所谓“最顺畅”,首先是指中途经停站最少;如果经停站一样多,则取需要换乘线路次数最少的路线。

输入格式:

输入在第一行给出一个正整数 \(N(\leq 100)\),即联盟公司的数量。接下来有 \(N\) 行,第 \(i\)\((i=1,⋯,N)\)描述了第i家公司所提供的线路。格式为:

M S[1] S[2] ⋯ S[M]

其中 \(M(\leq 100)\)是经停站的数量,\(S[i](i=1,⋯,M)\)是经停站的编号(由4位0-9的数字组成)。这里假设每条线路都是简单的一条可以双向运行的链路,并且输入保证是按照正确的经停顺序给出的 —— 也就是说,任意一对相邻的 \(S[i]\)\(S[i+1](i=1,⋯,M−1)\)之间都不存在其他经停站点。我们称相邻站点之间的线路为一个运营区间,每个运营区间只承包给一家公司。环线是有可能存在的,但不会不经停任何中间站点就从出发地回到出发地。当然,不同公司的线路是可能在某些站点有交叉的,这些站点就是客户的换乘点,我们假设任意换乘点涉及的不同公司的线路都不超过5条。

在描述了联盟线路之后,题目将给出一个正整数 \(K(\leq 10)\),随后 \(K\) 行,每行给出一位客户的需求,即始发地的编号和目的地的编号,中间以一空格分隔。

输出格式:

处理每一位客户的需求。如果没有现成的线路可以使其到达目的地,就在一行中输出“Sorry, no line is available.”;如果目的地可达,则首先在一行中输出最顺畅路线的经停站数量(始发地和目的地不包括在内),然后按下列格式给出旅行路线:

Go by the line of company #X1 from S1 to S2.
Go by the line of company #X2 from S2 to S3.
......

其中 Xi 是线路承包公司的编号,Si 是经停站的编号。但必须只输出始发地、换乘点和目的地,不能输出中间的经停站。题目保证满足要求的路线是唯一的。

Solution

Dfs就好了,用vector存图和路径属于哪个公司。

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, k, a, b, s, t, vis[MAXN], best_across = INF, best_change = INF;
struct node{ int next, company; };
vector<node>road[MAXN];
vector<pair<int, int> >path, best_path;

void dfs(int x, int across, int change, int company) {
    if(across > best_across || (across == best_across && change > best_change)) return ;
    if(x == t) {
        if(across < best_across || (across == best_across && change < best_change)){
            best_across = across;
            best_change = change;
            best_path = path;
        }
        return ;
    }
    for(int i = 0; i < road[x].size(); i++) {
        int next_road = road[x][i].next;
        int next_company = road[x][i].company;
        
        if(vis[next_road]) continue;
        
        vis[next_road] = 1;
        
        if(next_company == company)
            dfs(next_road, across + 1, change, next_company);
        else {
            path.push_back({x, next_company});
            dfs(next_road, across + 1, change + 1, next_company);
            path.pop_back();
        }
        vis[next_road] = 0;
    }
}
int main(int argc, const char * argv[]) {
    n = read();
    for(int i = 1; i <= n; i++) {
        k = read();
        a = read();
        while(--k) {
            b = read();
            road[a].push_back({b, i});
            road[b].push_back({a, i});
            a = b;
        }
    }
    k = read();
    while(k--) {
        s = read(), t = read();
        memset(vis, 0, sizeof vis);
        path.clear();
        best_path.clear();
        best_across = INF;
        best_change = INF;
        
        vis[s] = 1;
        dfs(s, 0, 0, 0);
        if(!best_path.size()) puts ("Sorry, no line is available.");
        else {
            best_path.push_back({t, 0});
            cout<<best_across<<endl;
            for(int i = 0; i < best_path.size() - 1; i++) {
                printf ("Go by the line of company #%d from %04d to %04d.\n", best_path[i].second, best_path[i].first, best_path[i+1].first);
            }
        }
    }
}

L3-015 球队“食物链”

某国的足球联赛中有 \(N\) 支参赛球队,编号从 \(1\)\(N\)。联赛采用主客场双循环赛制,参赛球队两两之间在双方主场各赛一场。
联赛战罢,结果已经尘埃落定。此时,联赛主席突发奇想,希望从中找出一条包含所有球队的“食物链”,来说明联赛的精彩程度。“食物链”为一个1至N的排列 \(\{ T_1, T_2 \dots T_N\}\),满足:球队 \(T_1\) 战胜过球队 \(T_2\) ,球队 \(T_2\) 战胜过球队 \(T_3\) \(\dots\),球队 \(T_(N−1)\) 战胜过球队 \(T_N\),球队 \(T_N\) 战胜过球队 \(T_1\)

现在主席请你从联赛结果中找出“食物链”。若存在多条“食物链”,请找出字典序最小的。

注:排列 ${a_1, a_2, \dots a_N} \(在字典序上小于排列\){b_1, b_2, \dots b_N} $,当且仅当存在整数 \(K(1 \leq K\leq N)\),满足:\(a_K \leq b_K\) 且对于任意小于 \(K\) 的正整数 \(i\)\(a_i=b_i\)

输入格式:

输入第一行给出一个整数 \(N(2\leq N \leq 20)\),为参赛球队数。随后 \(N\) 行,每行 \(N\) 个字符,给出了 \(N\times N\) 的联赛结果表,其中第 \(i\) 行第 \(j\) 列的字符为球队 \(i\) 在主场对阵球队 \(j\) 的比赛结果:W 表示球队 \(i\) 战胜球队 \(j\)\(L\) 表示球队 \(i\) 负于球队 \(j\)D 表示两队打平,- 表示无效(当\(i=j\) 时)。输入中无多余空格。

输出格式:

按题目要求找到“食物链” \(T_1​, T_2 \dots T_N\),将这 \(N\) 个数依次输出在一行上,数字间以1个空格分隔,行的首尾不得有多余空格。若不存在“食物链”,输出 No Solution

Solution

直接暴力 DFS + 一点点剪枝就可以过。

剪枝:为选择的点一定有至少一个连向起点,否则当前选的序列就不合法。

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, mp[30][30], vis[30], st;
stack<int>stk;
bool Dfs(int x,  int d) {
    if(d == n && mp[x][st]) return true;
    if(d == n && !mp[x][st]) return false;
    //剪枝
    bool tag = false;
    for(int i = 1; i <= n; i++) {
        if(!vis[i] && mp[i][st]) tag = true;
    }
    if(tag == false) return false;
    
    for(int i = 1; i <= n; i++) {
        if(mp[x][i] && !vis[i]) {
            vis[i] = 1;
            if(Dfs(i, d + 1)) {
                stk.push(i);
                return true;
            }
            vis[i] = 0;
        }
    }
    return false;
}
int main() {
    n = read();
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            char c;
            cin>>c;
            if(c == 'L') mp[j][i] = 1;
            if(c == 'W') mp[i][j] = 1;
        }
    }
    bool flag = false;
    for(int i = 1; i <= n; i++) {
        vis[i] = 1;
        st = i;
        if(Dfs(i, 1)) {
            flag = true;
            stk.push(i);
            break;
        }
        vis[i] = 0;
    }
    if(!flag) printf("No Solution\n");
    else {
        printf("%d",stk.top());
        stk.pop();
        while(!stk.empty()) {
            printf(" %d",stk.top());
            stk.pop();
        }
    }
}

L3-016 二叉搜索树的结构

二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉搜索树。(摘自百度百科)
给定一系列互不相等的整数,将它们顺次插入一棵初始为空的二叉搜索树,然后对结果树的结构进行描述。你需要能判断给定的描述是否正确。例如将{ 2 4 1 3 0 }插入后,得到一棵二叉搜索树,则陈述句如“2是树的根”、“1和4是兄弟结点”、“3和0在同一层上”(指自顶向下的深度相同)、“2是4的双亲结点”、“3是4的左孩子”都是正确的;而“4是2的左孩子”、“1和3是兄弟结点”都是不正确的。

输入格式:

输入在第一行给出一个正整数 \(N(\leq 100)\),随后一行给出 \(N\) 个互不相同的整数,数字间以空格分隔,要求将之顺次插入一棵初始为空的二叉搜索树。之后给出一个正整数 \(M(\leq 100)\),随后 \(M\) 行,每行给出一句待判断的陈述句。陈述句有以下 6 种:

A is the root,即"A是树的根";
A and B are siblings,即"A和B是兄弟结点";
A is the parent of B,即"A是B的双亲结点";
A is the left child of B,即"A是B的左孩子";
A is the right child of B,即"A是B的右孩子";
A and B are on the same level,即"A和B在同一层上"。
题目保证所有给定的整数都在整型范围内。

输出格式:

对每句陈述,如果正确则输出 Yes,否则输出 No,每句占一行。

Solution

二叉搜索树概念题,先不做了,直接站代码吧 =_=

Code

#include<bits/stdc++.h>
using namespace std;

#define endl '\n'
map<int,int> mp;

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

struct node{
	int x, left, right;
	int flor;
	int fa;
}node[N];
int cnt;

void dfs(int x, int u)
{
	if(x < node[u].x) 
	{
		if(!node[u].left)
		{
			cnt++;
			node[cnt].x = x;
			node[u].left = cnt;
			node[cnt].fa = u;
			node[cnt].flor = node[u].flor + 1;
			mp[x] = cnt;
		}
		else{
			dfs(x, node[u].left);
		}
	}
	else
	{
		if(!node[u].right)
		{
			cnt++;
			node[cnt].x = x;
			node[u].right = cnt;
			node[cnt].fa = u;
			node[cnt].flor = node[u].flor + 1;
			mp[x] = cnt;
		}
		else{ 
			dfs(x, node[u].right);
		}
	}
}

signed main(){
	cin>>n;
	
	int x; cin>>x; //先确定根节点 
	node[1].x = x;
	node[1].flor = 1;
	mp[x] = 1;
	cnt = 1;
	
	for(int i=2;i<=n;i++)
	{
		cin>>x;
		dfs(x, 1);
	}
	
	cin>>m;
	string s;
	getline(cin, s);
	
	while(m--)
	{
		getline(cin, s);
		int x = 0, y = 0;
		
		if(s.find("root") != s.npos)
		{
			sscanf(s.c_str(), "%d is the root", &x);
			if(mp[x] == 1) cout<<"Yes\n";
			else cout<<"No\n";
		}
		if(s.find("siblings") != s.npos)
		{
			sscanf(s.c_str(), "%d and %d are siblings", &x, &y);
			x = mp[x], y = mp[y];
			
			if(!x || !y) cout<<"No\n"; //需要先判断该值是否在树中
			else if(node[x].fa != node[y].fa) cout<<"No\n";
			else cout<<"Yes\n";
		}
		if(s.find("parent") != s.npos)
		{
			sscanf(s.c_str(), "%d is the parent of %d", &x, &y);
			x = mp[x], y = mp[y];
			
			if(!x || !y) cout<<"No\n";
			else if(node[y].fa == x) cout<<"Yes\n";
			else cout<<"No\n";
		}
		if(s.find("left") != s.npos)
		{
			sscanf(s.c_str(), "%d is the left child of %d", &x, &y);
			x = mp[x], y = mp[y];
			
			if(!x || !y) cout<<"No\n";
			else if(node[y].left == x) cout<<"Yes\n";
			else cout<<"No\n";
		}
		if(s.find("right") != s.npos)
		{
			sscanf(s.c_str(), "%d is the right child of %d", &x, &y);
			x = mp[x], y = mp[y];
			
			if(!x || !y) cout<<"No\n";
			else if(node[y].right == x) cout<<"Yes\n";
			else cout<<"No\n";
		}
		if(s.find("level") != s.npos)
		{
			sscanf(s.c_str(), "%d and %d are on the same level", &x, &y);
			x = mp[x], y = mp[y];
			
			if(!x || !y) cout<<"No\n";
			else if(node[y].flor == node[x].flor) cout<<"Yes\n";
			else cout<<"No\n";
		}
	}
	
	return 0;
}

L3-017 森森快递

森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从 \(0\)\((N−1)\) 编号。由于道路限制,第 \(i\) 号城市\((i=0,\dots,N−2)\)与第 \((i+1)\) 号城市中间往返的运输货物重量在同一时刻不能超过 \(C_i\) 公斤。

公司开张后很快接到了 \(Q\) 张订单,其中j张订单描述了某些指定的货物要从 \(S_j\) 号城市运输到 \(T_j\)​号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中途卸货。

为了让公司整体效益更佳,森森想知道如何安排订单的运输,能使得运输的货物重量最大且符合道路的限制?要注意的是,发货时间有可能是任何时刻,所以我们安排订单的时候,必须保证共用同一条道路的所有货车的总重量不超载。例如我们安排 1 号城市到 4 号城市以及 2 号城市到 4 号城市两张订单的运输,则这两张订单的运输同时受 2-3 以及 3-4 两条道路的限制,因为两张订单的货物可能会同时在这些道路上运输。

输入格式:

输入在第一行给出两个正整数 \(N\)\(Q(2 \leq N \leq 10^5, 1≤Q≤10^5)\),表示总共的城市数以及订单数量。

第二行给出 \((N−1)\) 个数,顺次表示相邻两城市间的道路允许的最大运货重量 \(C_i(i=0,⋯,N−2)\)。题目保证每个 \(C_i\) 是不超过 \(2^{31}\) 的非负整数。

接下来 \(Q\) 行,每行给出一张订单的起始及终止运输城市编号。题目保证所有编号合法,并且不存在起点和终点重合的情况。

输出格式:

在一行中输出可运输货物的最大重量。

Solution

贪心 + 线段树

不是很好想这个贪心。

将区间按照右端点排序,然后依次跑每个区间。

用线段树维护区间最小值,支持区间修改,每次查询区间的时候,找到最小值,然后修改区间,并将最小值统计进入答案中。

贪心的证明没想出来。。。

Code

#include <bits/stdc++.h>
#define int long long
#define ls rt << 1
#define rs rt << 1 | 1
using namespace std;
const int MAXN = 5e5 + 5;
const int INF = (1ll<<60);
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int n, q, c[MAXN], ans;
struct Tree{int Min, tag;}t[MAXN << 2];
struct Seg{
    int x, y;
    bool operator < (const Seg&rhs)const {
        return y < rhs.y;
    }
}seg[MAXN];
void push_up(int rt) {
    t[rt].Min = min(t[ls].Min, t[rs].Min);
}
void build(int rt, int l, int r) {
    t[rt].tag = 0;
    if(l == r) {
        t[rt].Min = c[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    push_up(rt);
}
void push_down(int rt) {
    if(t[rt].tag == 0) return ;
    int tag = t[rt].tag;
    t[ls].tag += tag, t[rs].tag += tag;
    t[ls].Min += tag, t[rs].Min += tag;
    t[rt].tag = 0;
}
void update(int rt, int l, int r, int L, int R, int k) {
    if(l > R || r < L) return;
    if(L <= l && r <= R) {
        t[rt].tag += k, t[rt].Min += k;
        return ;
    }
    push_down(rt);
    int mid = (l + r) >> 1;
    update(ls, l, mid, L, R, k);
    update(rs, mid + 1, r, L, R, k);
    push_up(rt);
}
int query(int rt, int l, int r, int L, int R) {
    if(l > R || r < L) return INF;
    if(L <= l && r <= R) return t[rt].Min;
    push_down(rt);
    int mid = (l + r) >> 1, ret = INF;
    ret = min(ret, query(ls, l, mid, L, R));
    ret = min(ret, query(rs, mid + 1, r, L, R));
    return ret;
}
signed main() {
    n = read(), q = read();
    for(int i = 1; i < n; i++) c[i] = read();
    build(1, 1, n - 1);
    for(int i = 1; i <= q; i++) {
        seg[i].x = read(), seg[i].y = read();
        if(seg[i].x > seg[i].y) swap(seg[i].x, seg[i].y);
    }
    sort(seg + 1, seg + q + 1);
    for(int i = 1; i <= q; i++){
        int res = query(1, 1, n - 1, seg[i].x + 1, seg[i].y);
        ans += res;
        if(res)update(1, 1, n - 1, seg[i].x + 1, seg[i].y, -res);
    }
    cout<<ans<<endl;
}

L3-018 森森美图

森森最近想让自己的朋友圈熠熠生辉,所以他决定自己写个美化照片的软件,并起名为森森美图。众所周知,在合照中美化自己的面部而不美化合照者的面部是让自己占据朋友圈高点的绝好方法,因此森森美图里当然得有这个功能。 这个功能的第一步是将自己的面部选中。森森首先计算出了一个图像中所有像素点与周围点的相似程度的分数,分数越低表示某个像素点越“像”一个轮廓边缘上的点。 森森认为,任意连续像素点的得分之和越低,表示它们组成的曲线和轮廓边缘的重合程度越高。为了选择出一个完整的面部,森森决定让用户选择面部上的两个像素点A和B,则连接这两个点的直线就将图像分为两部分,然后在这两部分中分别寻找一条从A到B且与轮廓重合程度最高的曲线,就可以拼出用户的面部了。 然而森森计算出来得分矩阵后,突然发现自己不知道怎么找到这两条曲线了,你能帮森森当上朋友圈的小王子吗?

为了解题方便,我们做出以下补充说明:

  • 图像的左上角是坐标原点 (0,0),我们假设所有像素按矩阵格式排列,其坐标均为非负整数(即横轴向右为正,纵轴向下为正)。

  • 忽略正好位于连接A和B的直线(注意不是线段)上的像素点,即不认为这部分像素点在任何一个划分部分上,因此曲线也不能经过这部分像素点。

  • 曲线是八连通的(即任一像素点可与其周围的8个像素连通),但为了计算准确,某像素连接对角相邻的斜向像素时,得分额外增加两个像素分数和的 \(\sqrt2\) 倍减一。例如样例中,经过坐标为 (3,1)(4,2) 的两个像素点的曲线,其得分应该是这两个像素点的分数和 \((2+2)\),再加上额外的 \((2+2)\) 乘以 \((\sqrt2−1)\),即约为 \(5.66\)

输入格式:

输入在第一行给出两个正整数 \(N\)\(M(5\leq N,M\leq 100)\),表示像素得分矩阵的行数和列数。

接下来 \(N\) 行,每行 \(M\) 个不大于 \(1000\) 的非负整数,即为像素点的分值。

最后一行给出用户选择的起始和结束像素点的坐标 \((X_{start},Y_{start})\)\((X_{end},Y_{end})\)。4 个整数用空格分隔。

输出格式:

在一行中输出划分图片后找到的轮廓曲线的得分和,保留小数点后两位。注意起点和终点的得分不要重复计算。

Solution

这题是有点难读,不过读懂了也就 挺简单了

题目大意就是在图中找两个点作为起点和终点,将这个两个点连线,直线将途中的点分为了两部分,然后在这两部分分别跑最短路。

这个题最大的收获是如何判断一个点位于这条直线的哪一侧:

可以用向量叉乘来判断

设矢量 \(P(x_1,y_1)\), \(Q(x_2,y_2)\),则P叉乘Q表示以这两个矢量为相邻边构成的平行四边形的面积。

\((x_1y_2-x_2y_1)\)

\(x_1y_2-x_2y_1>0\):表示 \(Q\)\(P\) 的逆时针方向;

\(x_1y_2-x_2y_1<0\):表示 \(Q\)\(P\) 的顺时针方向;

\(x_1y_2-x_2y_1=0\):表示 \(Q\)\(P\) 共线,但可能同向也可能反向。

两个注意的点:

  • 当 “斜着” 走的时候,权值要额外加上 \((\sqrt2 - 1) * (mp[x][y] + mp[tx][ty])\)

  • 给出的图坐标是 \(x\)\(y\) 是反着的

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const int dx[8] = {0, 0, 1, -1, 1, -1, 1, -1};
const int dy[8] = {1, -1, 0, 0, 1, -1, -1, 1};
const double r = sqrt(2) - 1;
const double rr[8] = {0, 0, 0, 0, r, r, r, r};
int read(){
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}
int N, M,  sx, sy, ex, ey, c[110][110], flag;
double mp[110][110];
int Check(int x, int y) {
    int d = (sx - x) * (ey - y) - (ex - x) * (sy - y);
    if(d) return d / abs(d);
    return 0;
}
bool vis[110][110];
double num[110][110];
double Bfs() {
    memset(vis, 0, sizeof vis);
    
    for(int i = 0; i < N; i++)
        for(int j = 0; j < M; j++) num[i][j] = INF;

    num[sx][sy] = mp[sx][sy];
    queue<pair<pair<int, int>, double>>q;
    q.push({{sx, sy}, num[sx][sy]});
    while(!q.empty()) {
        int x = q.front().first.first;
        int y = q.front().first.second;
        double sum = q.front().second;
        q.pop();
        for(int i = 0; i < 8; i++) {
            int tx = x + dx[i];
            int ty = y + dy[i];
            if(tx < 0 || ty < 0 || tx >= N || ty >= M || c[tx][ty] != flag) continue;
            double tsum = sum + mp[tx][ty] + rr[i] * (mp[x][y] + mp[tx][ty]);
            if(tsum < num[tx][ty]) {
                num[tx][ty] = tsum;
                q.push({{tx, ty}, tsum});
            }
        }
    }
    return num[ex][ey];
}
int main(){
        
    N = read(), M = read();
    
    for(int i = 0; i < N; i++)
        for(int j = 0; j < M; j++) mp[i][j] = read();
    
    sy = read(), sx = read(), ey = read(), ex = read();
    
    for(int i = 0; i < N; i++)
        for(int j = 0; j < M; j++) c[i][j] = Check(i, j);

    double ans = 0 - mp[sx][sy] - mp[ex][ey];
    
    c[sx][sy] = c[ex][ey] = flag = 1;
    ans += Bfs();
        
    c[sx][sy] = c[ex][ey] = flag = -1;
    ans += Bfs();
    printf("%.2f",ans);
        
}

posted @ 2025-04-17 21:46  Dita  阅读(42)  评论(0)    收藏  举报