2019-2020 ICPC Northwestern European Regional Programming Contest (NWERC 2019)

Preface

由于前两天疑似感染风寒,今天头痛+鼻塞+咽炎一条龙,硬打了4h顶不住就下班了

最后过了8个题也还行,比较可惜的就是H题从中期写到结束,祁神和徐神各写了一个version也没过


A. Average Rank

挺有意思的一个题

考虑将一个原来分数为\(x\)的人加\(1\)分后会发生什么,显然只有原来分数也为\(x\)分的人的排名往后移了一位

直接暴力维护是\(O(nw)\)的,但我们仔细一想可以记录一下每个分数上一次更新的时间,以此略过不受影响的排名

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int n,w,x,y,p[N],rk[N],lst[N],sum[N],coef[N],pre[N];
signed main()
{
	RI i,j; for (scanf("%lld%lld",&n,&w),i=0;i<=w;++i) lst[i]=1;
	for (i=1;i<=w;++i)
	{
		for (scanf("%lld",&x),j=1;j<=x;++j)
		{
			scanf("%lld",&y); coef[p[y]]+=rk[p[y]]*(i-lst[p[y]]);
			lst[p[y]]=i; ++rk[p[y]]; sum[y]+=coef[p[y]]-pre[y];
			++p[y]; coef[p[y]]+=rk[p[y]]*(i-lst[p[y]]);
			lst[p[y]]=i; pre[y]=coef[p[y]];
		}
	}
	for (i=1;i<=n;++i)
	{
		coef[p[i]]+=rk[p[i]]*(w+1-lst[p[i]]);
		lst[p[i]]=w+1; sum[i]+=coef[p[i]]-pre[i];
		printf("%.9lf\n",(1.0*sum[i]/w+1.0));
	}
	return 0;
}

B. Balanced Cut

应该是本场的防AK题,做不来一点


C. Canvas Line

徐神写的,我题目都没看

#include <bits/stdc++.h>

int n, p;
std::map<int, std::pair<int, int>> canvas;
std::set<int> pegs, at;

using mit = std::map<int, std::pair<int, int>>::iterator;

mit get_next(mit it) {
    if(it == canvas.end()) return it;
    int r = it->second.first;
    ++it; if(it == canvas.end()) return it;
    return it->first == r ? it : canvas.end();
}

bool try_put(int x, bool ne = false) {
    if(pegs.find(x) != pegs.end()) return false;
    auto it = canvas.lower_bound(x);
    if(x == it->first) {
        if(it->second.second == 2) return false;
        if(it != canvas.begin()) {
            auto pre = it; --pre;
            if(pre->second.first == x) {
                if(pre->second.second == 2) return false;
                pre->second.second += 1;
                ne = true;
            }
        }
        it->second.second += 1; ne = true;
    } else if(it != canvas.begin()) {
        --it;
        if(it->first <= x && x <= it->second.first) {
            if(it->second.second == 2) return false;
            it->second.second += 1; ne = true;
        } 
    }
    if(ne) pegs.insert(x);
    return true;
}

int main(void) {
    std::ios::sync_with_stdio(false);
    std::cin >> n;
    for(int i = 0, l, r; i < n; ++i) {
        std::cin >> l >> r;
        canvas[l] = {r, 0};
    }
    std::cin >> p;
    for(int i = 0, x; i < p; ++i) {
        std::cin >> x; at.insert(x);
        if(!try_put(x, true)) return std::cout << "impossible\n", 0;
    }
    for(auto &[l, val]: canvas) {
        auto &[r, count] = val;
        if(count > 2) return std::cout << "impossible\n", 0;
        int x = r;
        while(count < 2 && x >= l) try_put(x--);
        if(x < l) return std::cout << "impossible\n", 0;
    }
    std::vector<int> pgs; for(auto p: pegs) if(at.find(p) == at.end()) pgs.push_back(p);
    std::cout << pgs.size() << char(10);
    for(int i = 0; i < pgs.size(); ++i) std::cout << pgs[i] << char(i == pgs.size() - 1 ? 10 : 32);    
    return 0;
}

D. Disposable Switches

挺有意思的一个题

首先可以发现题目中给出的两个参数一起可以统一为一个,即将所有边权整体乘上\(v\)后得到\(l+cv\),将\(cv\)整体看作一个变量

注意到\(cv\)的贡献之和所经过的边数有关,因此考虑在跑最短路的时候把这个信息保留下来

不妨设\(f_{i,j}\)表示从\(1\)号点走到\(i\)号点,恰好经过\(j\)条边的最短路,这个随便DP一下就能求出

假设最后某条\(1\to n\)的最短路恰经过了\(k\)条边,将\(cv\)看作自变量\(x\)\(f_{n,k}\)看作截距,则可以得到一条直线\(y=kx+f_{n,k}\)

不难发现将所有的直线画出来后最后可能称为最短路的就是一个半平面交构成的区域

找出所有在半平面上的直线,倒着还原一遍DP过程即可找出所有的关键点

总复杂度\(O(nm)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=2005,INF=1e18;
int n,m,x,y,z,f[N][N],key[N],vis[N][N]; vector <pi> v[N];
inline int Cross(const pi& A,const pi& B,const pi& C)
{
	return (B.fi-A.fi)*(C.se-A.se)-(C.fi-A.fi)*(B.se-A.se);
}
inline void travel(CI now,CI len,CI dis)
{
	if (vis[now][len]) return; vis[now][len]=1;
	key[now]=1; for (auto [pre,w]:v[now])
	if (f[pre][len-1]+w==dis) travel(pre,len-1,f[pre][len-1]);
}
signed main()
{
	RI i,j; for (scanf("%lld%lld",&n,&m),i=1;i<=m;++i)
	scanf("%lld%lld%lld",&x,&y,&z),v[x].push_back(pi(y,z)),v[y].push_back(pi(x,z));
	for (i=1;i<=n;++i) for (j=0;j<=n;++j) f[i][j]=INF;
	for (f[1][0]=0,j=0;j<n;++j) for (i=1;i<=n;++i) if (f[i][j]!=INF)
	for (auto [to,w]:v[i]) f[to][j+1]=min(f[to][j+1],f[i][j]+w);
	vector <pi> stk; for (i=n;i>=1;--i) if (f[n][i]!=INF)
	{
		pi cur=pi(i,f[n][i]);
		while (stk.size()>=2&&Cross(stk[stk.size()-2],stk.back(),cur)>0) stk.pop_back();
		if (stk.size()==1&&stk.back().se>cur.se) stk.pop_back();
		stk.push_back(cur);
	}
	for (auto [len,dis]:stk) travel(n,len,dis);
	vector <int> ans; for (i=1;i<=n;++i) if (!key[i]) ans.push_back(i);
	printf("%lld\n",ans.size()); for (auto x:ans) printf("%lld ",x);
	return 0;
}

E. Expeditious Cubing

徐神开场写的,WA了两发,看来有不少坑点

#include <bits/stdc++.h>

int rd() {
    long double s; std::cin >> s;
    return std::round(s * 100.L);
}

int main() {
    std::vector<int> a(4);
    for(int &a: a) a = rd();
    std::sort(a.begin(), a.end());
    int b = rd() * 3;
    // if(a[0] + a[1] + a[2] >= b) return std::cout << "infinite\n", 0;
    // if(a[1] + a[2] + a[3] < b) return std::cout << "impossible\n", 0;
    int ans = a[0] - 1;
    for(int i = a[3] + 1; i >= a[0]; --i) {
        std::vector<int> c = a; c.push_back(i);
        std::sort(c.begin(), c.end());
        if(c[1] + c[2] + c[3] <= b) { ans = i; break; }
    }
    if(ans == a[0] - 1) return std::cout << "impossible\n", 0;
    if(ans == a[3] + 1) return std::cout << "infinite\n", 0;
    std::cout << ans / 100 << "." << (ans % 100 < 10 ? "0" : "") << ans % 100 << char(10);
    return 0;
}

F. Firetrucks Are Red

祁神开场写的,我题目都没看

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

const int N = 2e5+5;
int n, fa[N];
int gf(int x){return x==fa[x] ? x : fa[x]=gf(fa[x]);}
map<int, int> mp; int idx=0;
vector<int> G[N];
struct Node{
	int a, b, p;	
};
vector<Node> ans;

signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n;
	idx=0;
	for (int i=1; i<=n; ++i) fa[i]=i;
	for (int i=1; i<=n; ++i){
		int m; cin >> m;
		for (int j=1; j<=m; ++j){
			int a; cin >> a;
			if (!mp.count(a)) mp[a]=++idx;
			G[mp[a]].push_back(i);
		}
	}
	
	for (auto [x, id] : mp){
		int sz=G[id].size();
		for (int j=1; j<sz; ++j){
			if (gf(G[id][j])!=gf(G[id][0])){
				ans.push_back(Node{G[id][j], G[id][0], x});
				fa[gf(G[id][j])] = gf(G[id][0]);
			}
		}
	}	
	bool ok=true;
	for (int i=2; i<=n; ++i) if (gf(i)!=gf(1)){ok=false; break;}
	if (!ok) cout << "impossible\n";
	else{
		for (auto [a, b, p] : ans){
			cout << a << ' ' << b << ' ' << p << '\n';	
		}	
	}
	return 0;
}

G. Gnoll Hypothesis

这题的题意比较鬼畜,我刚开始看错了好几个版本的题意,后面让祁神再读了一遍才搞懂

考虑枚举圆环上两个相邻的选中位置\(x,y(x<y)\),此时\(s'_y=\sum_{i=x+1}^y s_i\),这种局面出现的概率就是\(\frac{C_{n-(y-x+1)}^{k-2}}{C_n^k}\)

注意特判\(k=1\)的情形

#include<bits/stdc++.h>
using namespace std;
using LD = long double;

const int N = 505;
int n, k;
LD sum[N][N];
LD C[N][N];
LD ans[N];
signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cout << setiosflags(ios::fixed) << setprecision(10);
	cin >> n >> k;
	
	if (1==k){
		for (int i=0; i<n; ++i) cout << 1.0L/n*100 << ' ';
		cout << '\n';
		return 0;
	}
	
	C[0][0] = C[1][0] = C[1][1] = 1.0L;
	for (int i=2; i<=n; ++i){
		C[i][0] = C[i][i] = 1.0L;
		for (int j=1; j<i; ++j) C[i][j] = C[i-1][j-1] + C[i-1][j];
	}
	
	for (int i=0; i<n; ++i) cin >> sum[i][0];
	for (int j=1; j<n-1; ++j){
		for (int i=0; i<n; ++i){
			sum[i][j] = sum[i][j-1]+sum[(i+n-j)%n][0];	
		}
	}
	
	
	for (int i=0; i<n; ++i){
		for (int x=0; x<=n-k; ++x){
			ans[i] += C[n-x-2][k-2] / C[n][k] * sum[i][x];	
		}
	}	
	for (int i=0; i<n; ++i) cout << ans[i] << ' '; 
	cout << '\n';
	
	return 0;
}

H. Height Profile

这题看数据范围就是允许用\(O(nk\log n)\)的做法的,因此我们考虑对于每个询问分开处理

对于两点\((x_1,y_1),(x_2,y_2)(x_1<x_2)\),需要满足\(\frac{y_2-y_1}{x_2-x_1}\ge g\),移项后得到\(y_2-g\cdot x_2\ge y_1-g\cdot x_1\)

不妨设\(y'=y-g\cdot x\),则原问题等价于找出一段最长的区间\([l,r]\)使得\(y'_r-y'_l\ge 0\)

然后祁神和徐神写了两个version的扫描线,都不知道挂在哪里了,坐等队友补题了


I. Inverted Deck

考虑令\(b\)为排序后的\(a\)数组,原问题等价于要翻转\(b\)的某段区间使其等于\(a\)

找到\(a,b\)两个数组不相同位置的左右端点,翻转这个区间后检验是否相同即可

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

const int N = 1e6+5;
int n, A[N], B[N];
signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n;
	for (int i=1; i<=n; ++i){
		int a; cin >> a;
		A[i] = B[i] = a;
	}
	sort(B+1, B+n+1);
	int L=-1, R=-1;
	for (int i=1; i<=n; ++i){
		if (A[i]!=B[i]){
			if (-1==L) L=i;
			R=i;
		}
	}
	
	if (-1==L) cout << "1 1\n";
	else{
		bool ok=true;
		for (int i=L; i<=R; ++i){
			if (A[i] != B[L+R-i]){ok=false; break;}	
		}
		if (!ok) cout << "impossible\n";
		else cout << L << ' ' << R << '\n';
	}
	return 0;
}

J. Jackdaws And Crows

很有意思的一个题

考虑如果我们确定了建小号的次数为\(x\),怎么求此时需要删除的帖子数的最小值

不难发现此时可以把所有数分为三类:

  • 无论怎么操作都为正数,记为+
  • 无论怎么操作都为负数,记为-
  • 可以人为操控其正负性,记为?

而对于一段由上述三种字符构成的序列,要求其最少需要删除多少个字符,使得将所有的?赋值后得到一个加减交错的序列

手玩归纳一下会发现一个很好的性质,对于两个相邻的+-,设它们中间有\(t\)?(可以为\(0\)个),当

  • 相邻的两个符号不同且\(t\)是奇数时,需要删除一个字符
  • 相邻的两个符号相同且\(t\)是偶数时,需要删除一个字符

刚开始想了一堆繁琐的方法来维护这个东西,后面意识到可以从小到大枚举\(x\),此时的操作只有+-变成?

拿一个链表去维护序列中所有的+-,代码十分好写

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<utility>
#define int long long
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=500005;
int n,c,r,s[N],L[N],R[N],ans,ret; pi p[N];
inline int sgn(CI x)
{
	if (x>0) return 1; if (x<0) return -1; return 0;
}
inline void remove(CI x)
{
	R[L[x]]=R[x]; L[R[x]]=L[x];
}
inline int calc(CI x,CI y)
{
	if (x<1||x>n||y<1||y>n) return 0;
	if (sgn(s[x])!=sgn(s[y])&&(y-x-1)%2==1) return r;
	if (sgn(s[x])==sgn(s[y])&&(y-x-1)%2==0) return r;
	return 0;
}
signed main()
{
	RI i; for (scanf("%lld%lld%lld",&n,&c,&r),i=1;i<=n;++i)
	scanf("%lld",&s[i]),p[i]=pi(abs(s[i]),i);
	int lst=0; for (i=1;i<=n;++i)
	{
		if (!s[i]) { ans+=r; continue; }
		if (lst&&sgn(s[lst])==sgn(s[i])) ans+=r; lst=i;
	}
	for (i=1;i<=n;++i) L[i]=i-1,R[i]=i+1; R[0]=1; L[n+1]=n;
	for (lst=0,i=1;i<=n;++i)
	if (!s[i]) remove(i); else ret+=calc(lst,i),lst=i;
	for (sort(p+1,p+n+1),i=1;i<=n;++i)
	for (ans=min(ans,c+ret),i=1;i<=n;++i) if (p[i].fi)
	{
		auto [val,pos]=p[i]; ret-=calc(L[pos],pos)+calc(pos,R[pos]);
		ret+=calc(L[pos],R[pos]); remove(pos); ans=min(ans,c*(val+1)+ret);
	}
	return printf("%lld",ans),0;
}

K. Kitesurfing

又是防AK题,做不来一点


Postscript

今天状态确实不太好,估计明天要稍微休息一下了的说

posted @ 2024-02-04 19:07  空気力学の詩  阅读(49)  评论(0编辑  收藏  举报