2019-2020 ICPC Southwestern European Regional Programming Contest (SWERC 2019-20)

Preface

这场总体打的不错,虽然最后Rush L题失败,没有想到关键优化导致没卡过去有点可惜,但奈何徐神还是太C了

最后10题下班,赛后祁神发现L关键优化10min改完就过了,同时赛中徐神也看出了E的做法,感觉这场时间充足甚至有AK的可能的说


A. Environment-Friendly Travel

很典的一个题,不难发现直接以当前在哪个点,已经走了多少公里为状态是可以接受的

转移的话就写一个类似Dijkstra的东西即可,复杂度\(O(NB\log (NB))\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005,B=105,INF=1e9;
struct edge
{
	int to,len,tp;
}; int n,m,lim,f[N][B],vis[N][B],c[N],x[N],y[N]; vector <edge> v[N];
struct ifo
{
	int val,id,dis;
	friend inline bool operator < (const ifo& A,const ifo& B)
	{
		return A.val>B.val;
	}
}; priority_queue <ifo> hp;
int main()
{
	RI i,j; scanf("%d%d%d%d%d%d",&x[1],&y[1],&x[2],&y[2],&lim,&c[0]);
	for (scanf("%d",&m),i=1;i<=m;++i) scanf("%d",&c[i]);
	for (scanf("%d",&n),i=3;i<=n+2;++i)
	{
		int sz; scanf("%d%d%d",&x[i],&y[i],&sz);
		for (j=1;j<=sz;++j)
		{
			int y,tp; scanf("%d%d",&y,&tp); y+=3;
			
			v[i].push_back((edge){y,0,tp}); v[y].push_back((edge){i,0,tp});
		}
	}
	auto getdis=[&](CI u,CI v)
	{
		return ceil(sqrt((x[u]-x[v])*(x[u]-x[v])+(y[u]-y[v])*(y[u]-y[v])));
	};
	for (i=3;i<=n+2;++i) for (auto& [to,len,tp]:v[i]) len=getdis(i,to);
	for (i=2;i<=n+2;++i) v[1].push_back((edge){i,getdis(1,i),0});
	for (i=3;i<=n+2;++i) v[i].push_back((edge){2,getdis(i,2),0});
	for (i=1;i<=n+2;++i) for (j=0;j<=lim;++j) f[i][j]=INF;
	f[1][0]=0; hp.push((ifo){0,1,0});
	while (!hp.empty())
	{
		auto [_,now,dis]=hp.top(); hp.pop();
		if (vis[now][dis]) continue; vis[now][dis]=1;
		for (auto [to,len,tp]:v[now])
		if (dis+len<=lim&&f[now][dis]+c[tp]*len<=f[to][dis+len])
		hp.push((ifo){f[to][dis+len]=f[now][dis]+c[tp]*len,to,dis+len});
	}
	int ans=INF; for (i=0;i<=lim;++i) ans=min(ans,f[2][i]);
	return printf("%d",ans!=INF?ans:-1),0;
}

B. Biodiversity

签到(话说这题怎么和今年合肥的签到一模一样)

#include<cstdio>
#include<iostream>
#include<map>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
map <string,int> mp;
int main()
{
	ios::sync_with_stdio(false); cin.tie(0);
	int n; string s; cin>>n;
	for (RI i=1;i<=n;++i) cin>>s,++mp[s];
	for (auto [name,cnt]:mp)
	if (cnt*2>n) return cout<<name,0;
	return cout<<"NONE",0;
}

C. Ants

又是签到,但有人读不出nonnegative的含义真是FW啊

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,vis[N]; char s[105];
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
	{
		scanf("%s",s+1); int m=strlen(s+1);
		if (s[1]=='-'||m>=9) continue;
		int x=0; for (j=1;j<=m;++j) x=x*10+s[j]-'0';
		if (x<=n) vis[x]=1;
	}
	for (i=0;vis[i];++i);
	return printf("%d",i),0;
}

D. Gnalcats

徐神写的疑似大模拟的题,我不仅完全看不懂题目,甚至连徐神的代码都看不懂

#include <bits/stdc++.h>

class ProteinBase;
class SimpleProtein;
class ComplexProtein;

class ProteinBase {
public:
    virtual SimpleProtein* to_simple() {
        return nullptr;
    };
    virtual ComplexProtein* to_complex() {
        return nullptr;
    };
};

class SimpleProtein: public ProteinBase {
public:
    int val;
    virtual SimpleProtein* to_simple() { return this; }
} simple_base[1 << 20], *simple_p = simple_base;

class ComplexProtein: public ProteinBase {
public:
    ProteinBase *l, *r;
    virtual ComplexProtein* to_complex() { return this; }
} complex_base[1 << 20], *complex_p = complex_base;

bool is_same_protein(ProteinBase *&a, ProteinBase *b) {
    std::map<std::pair<ProteinBase*, ProteinBase*>, bool> cache;
    if(a == b) return true;
    if(a->to_simple() && b->to_simple()) {
        if(a->to_simple()->val == b->to_simple()->val)
            return a = b, true;
        else return false;
    }
    if(a->to_complex() && b->to_complex()) {
        auto it = cache.find({a, b});
        if(it != cache.end()) return it->second;
        auto ac = a->to_complex(), bc = b->to_complex();
        if(is_same_protein(ac->l, bc->l) && is_same_protein(ac->r, bc->r)) {
            a = b; return cache[{a, b}] = true;
        } else return cache[{a, b}] = false;
    }
    return false;
}

void fill(std::deque<ProteinBase*> &v, int &O, size_t target) {
    if(v.size() >= target) return ;
    int cur = target - v.size();
    for(int i = 0; i < cur; ++i) {
        auto nn = simple_p++;
        nn->val = O + i;
        v.push_front(nn);
    }
    O += cur;
    return ;
}

std::deque<ProteinBase*> work(std::string &ops, bool &fail, int &O) {
    std::deque<ProteinBase*> prc {}; O = 0;
    auto chs = [&] (size_t s) { fill(prc, O, s); };
    fail = false;
    ComplexProtein *cp;
    ProteinBase *a, *b;
    for(auto op: ops) {
        switch (op) {
            case 'C': chs(1);
                prc.push_back(prc.back()); break;
            case 'D': chs(1);
                prc.pop_back(); break;
            case 'L': chs(1);
                cp = prc.back()->to_complex(); prc.pop_back();
                if(!cp) { fail = true; return {}; }
                prc.push_back(cp->l); break;
            case 'P': chs(2);
                a = prc.back(); prc.pop_back();
                b = prc.back(); prc.pop_back();
                cp = complex_p++;
                cp->l = a; cp->r = b;
                prc.push_back(cp); break;
            case 'R': chs(1);
                cp = prc.back()->to_complex(); prc.pop_back();
                if(!cp) { fail = true; return {}; }
                prc.push_back(cp->r); break;
            case 'S': chs(2);
                a = prc.back(); prc.pop_back();
                b = prc.back(); prc.pop_back();
                prc.push_back(a), prc.push_back(b); break;
            case 'U': chs(1);
                cp = prc.back()->to_complex(); prc.pop_back();
                if(!cp) { fail = true; return {}; }
                prc.push_back(cp->r); prc.push_back(cp->l); break;
        }
    }
    return prc;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::string s1, s2;

    std::cin >> s1 >> s2;

    bool f1, f2;
    int O1, O2;
    std::deque<ProteinBase*> p1, p2;

    p1 = work(s1, f1, O1);
    p2 = work(s2, f2, O2);
    
    if(f1 && f2) return std::cout << "True" << std::endl, 0;
    if(!f1 && f2 || f1 && !f2)
        return std::cout << "False" << std::endl, 0;

    if(p1.size() < p2.size()) fill(p1, O1, p2.size());
    else                      fill(p2, O2, p1.size());

    // std::cerr << p1.size() << ' ' << p2.size() << char(10);

    // std::cerr << p1.end()[-1]->to_simple()->val << ' ' << p1.end()[-2]->to_simple()->val << std::endl;
    // std::cerr << p2.end()[-1]->to_simple()->val << ' ' << p2.end()[-2]->to_simple()->val << std::endl;
    
    // std::cerr << O1 << ' ' << O2 << std::endl;

    for(int i = 0; i < p1.size(); ++i) if(!is_same_protein(p1[i], p2[i]))
        return std::cout << "False" << std::endl, 0;

    std::cout << "True" << std::endl;

    return 0;
}

E. Pixels

好像是个典题

关灯模型其实只要第一行的灯的初始状态确定后解就唯一确定了

不妨把第一行看作未知向量,跑一个0/1高斯消元即可

这题的补题已经被徐神预定了,我就看个乐了


F. Icebergs

按题意套多边形面积的板子即可

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

struct Pt{
	int x, y;
	Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};}
	int crs(Pt b)const{return x*b.y-y*b.x;}
};

int n;
signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n;
	int ans=0;
	for (int i=1; i<=n; ++i){
		int sz; cin >> sz;
		vector<Pt> vec(sz);
		for (int j=0; j<sz; ++j) cin >> vec[j].x >> vec[j].y;
		vec.push_back(vec[0]);
		int res=0;
		for (int j=1; j<=sz; ++j){
			
			int tmp = vec[j].crs(vec[j-1]);
			res += tmp;
//			printf("(%lld %lld)\n", vec[j].x, vec[j].y);
		}
		ans += abs(res);
//		printf("i=%lld res=%lld ans=%lld\n", i, res, ans);
	}
	cout << ans/2 << '\n';
	return 0;	
}

G. Swapping Places

挺有意思的一个题

刚开始看到字典序最小想着用经典的从前往后按位确定,但发现这题固定前缀后后面的状态不好确定

后面祁神发现一个关键思路,顺着想了下就会做了

考虑对于两个位置\(x,y(x<y)\),若\(x,y\)对应的物种间不能交换,则最后\(x\)的位置一定在\(y\)之前

因此有个naive的想法就是把每个位置看作一个点,然后将每个点向它后面的并且物种间不能交换的位置连有向边,最后跑一个字典序最小的合法拓扑序即可

但直接暴力地处理边数是\(O(N^2)\)的,但我们稍作思考会发现其实只要向后面的每个物种的最近的位置连边即可,这样边数是\(O(NS)\)

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

int S, L, n;
const int N = 1e5+5;
vector<string> vec;
bool fri[205][205];
int lst[205];
int Q[N], in[N];
vector<int> G[N];
struct Node{
	int typ, pos;
	bool operator<(const Node &b)const{return typ!=b.typ ? typ>b.typ : pos>b.pos;}
};
priority_queue<Node> Qry;
vector<int> ans;

int getid(string &str){return lower_bound(vec.begin(), vec.end(), str)-vec.begin();}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> S >> L >> n;
	vec.resize(S);
	for (int i=0; i<S; ++i) cin >> vec[i];
	sort(vec.begin(), vec.end());
	for (int i=0; i<L; ++i){
		string s1, s2; cin >> s1 >> s2;
		int a=getid(s1), b=getid(s2);
		fri[a][b] = fri[b][a] = true;
	}

	for (int i=1; i<=n; ++i){
		string s; cin >> s;
		Q[i] = getid(s);
	}
	
	for (int i=1; i<=n; ++i){
		for (int j=0; j<S; ++j){
			if (!fri[j][Q[i]]){
				G[lst[j]].push_back(i);
				++in[i];
			}
		}
		lst[Q[i]] = i;
	}
	Qry.push(Node{-1, 0});
	while (!Qry.empty()){
		auto [typ, pos] = Qry.top(); Qry.pop();
		ans.push_back(typ);
		for (int v : G[pos]){
			--in[v];
			if (!in[v]) Qry.push(Node{Q[v], v});
		}
	}
	for (int i=1; i<=n; ++i){
		cout << vec[ans[i]] << ' ';
	}cout << '\n';
	
	return 0;	
}

H. Pseudo-Random Number Generator

ORZ徐神光速秒了这题,给我后面写题留足了时间(虽然L还是没过)

这题说起来没啥思维含量,就是本机暴力跑出循环节后分段打表,算是个考验乱搞水平的题

#include <bits/stdc++.h>

using llui = long long unsigned int;

std::unordered_set<llui> s;

inline llui f(llui x) {
    return x + (x >> 20) + 12345 & 1099511627775ull;
}

constexpr llui m1 = 350125310, m2 = 182129209, mx = 516914, mt = 91029304;

int main() {
    std::ios::sync_with_stdio(false);
    llui x = 0x600DCAFEull, e = 0;
    llui n, i; std::cin >> n;
    if(n >= 300000000)
        i = 300000000, e = 150119936, x = 29466792348ull; else
    if(n >= 200000000)
        i = 200000000, e = 100089173, x = 565345893787ull; else
    if(n >= 100000000)
        i = 100000000, e = 50056830, x = 78802032994ull;
    for(; i < n && i < m1; ++i, x = f(x)) {   
        // if(i % 100000000 == 0) std::cerr << "i: " << i << " x = " << x << char(10);
        if(x == mx) std::cerr << "i = " << i << char(10);
        e += ~x & 1;
    }
    if(n > i) {
        llui s = (n - i) / m2;
        e += s * mt;
        i += s * m2;
    }
    n -= i; i = 0;//  int ne = 0;
    if(n >= 120000000)
        i = 120000000, e += 59985790, x = 274358548202ull; else
    if(n >=  60000000)
        i =  60000000, e += 29971111, x = 552368730897ull;
    for(; i < n; ++i, x = f(x)) {
        // if(i % 60000000 == 0) std::cerr << "i = " << i << ", e = " << ne << ", x = " << x << ";\n";
        e += ~x & 1;
        // ne += ~x & 1;
    }
    std::cout << e << char(10);
    return 0;
}

I. Rats

纯签到

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
	int n1,n2,n12; scanf("%d%d%d",&n1,&n2,&n12);
	return printf("%d",(n1+1)*(n2+1)/(n12+1)-1),0;
}

J. Counting Trees

感谢龙7在海猫中教的“把棋盘翻转过来”的思考法,成功避免了难写的做法

由于题目给的是个小根堆性质,因此对于一个区间,我们找出其最小值以及个数

不难发现最后树的形态就是先将若干个最小值组成合法的二叉树,然后再递归处理子区间

若干个点构成的本质不同的二叉树数量很经典,由二叉树的括号序列表示法我们知道方案数就是卡特兰数

但直接从小到大处理的话代码写起来很繁琐(而且复杂度好像也是假的),因此不妨考虑更本质的做法

考虑对于值相同的两个位置,什么时候它们会一起算贡献,显然当且仅当这两个数之间不存在小于它们的数

那做法就很简单了,直接用ST表维护下区间静态最小值即可,最后枚举判断

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=2e6+5,mod=1e9+7;
int n,a[N],mn[N][21],_log[N],ans=1,fact[N],ifac[N]; vector <int> pos[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	if (n<0||m<0||n<m) return 0;
	return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline int Catalan(CI n)
{
	return (C(2*n,n)-C(2*n,n-1)+mod)%mod;
}
inline int getmin(CI l,CI r)
{
	if (l>r) return 1e9; int k=_log[r-l+1];
	return min(mn[l][k],mn[r-(1<<k)+1][k]);
}
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d",&a[i]),mn[i][0]=a[i],pos[a[i]].push_back(i);
	for (_log[0]=-1,i=1;i<=n;++i) _log[i]=_log[i>>1]+1;
	for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i)
	mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]);
	for (init(2*n),i=0;i<=1000000;++i) if (!pos[i].empty())
	{
		int sz=1; for (j=1;j<pos[i].size();++j)
		{
			int l=pos[i][j-1],r=pos[i][j];
			if (getmin(l,r)<i) ans=1LL*ans*Catalan(sz)%mod,sz=1; else ++sz;
		}
		ans=1LL*ans*Catalan(sz)%mod;
	}
	return printf("%d",ans),0;
}

K. Birdwatching

首先题目等价于找符合以下条件的点\(x\)的个数:

  • 存在\(x\to T\)的边
  • 删除\(x\to T\)的边后\(x\)无法到达\(T\)

考虑把原图中所有边取反,我们不妨将\(T\)相连的点依次“激活”

每次激活一个点时就顺带激活所有它能到达的点,最后如果一个存在\(T\to x\)的点\(x\)只被激活了一次就符合要求

暴力做的话复杂度是\(O(n^2)\)的,但稍作思考就会发现当一个点被激活了两次以上后就不需要再处理它了,因此可以优化到\(O(n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,t,x,y,c[N],clk[N]; vector <int> v[N];
inline void DFS(CI now,CI idx)
{
	if (now==t) return;
	if (clk[now]==idx) return; clk[now]=idx;
	if (c[now]>=2) return; ++c[now];
	for (auto to:v[now]) DFS(to,idx);
}
int main()
{
	RI i; scanf("%d%d%d",&n,&m,&t); ++t;
	for (i=1;i<=m;++i) scanf("%d%d",&x,&y),++x,++y,v[y].push_back(x);
	for (auto x:v[t]) DFS(x,x); vector <int> ans;
	for (auto x:v[t]) if (c[x]==1) ans.push_back(x);
	printf("%d\n",ans.size());
	for (auto x:ans) printf("%d\n",x-1);
	return 0;
}

L. River Game

这题其实按思维难度来看非常简单,但就是要想到关键的优化来降低复杂度

首先数据范围不大,同时题目中给出的限制等价于每个水域相互独立,因此可以用SG函数模型求解

不妨先暴力找出相连的水域,然后将其周围的可以放摄像机的空地全部找出来

用状压处理状态后就可以用SG函数求解了,设空地的数目为\(m\),则复杂度为\(O(2^m\times m)\)

然后刚开始naive的以为每个水域对应的空地数\(m\)\(2n\)级别的,结果写了个交了发后才发现是\(4n\)级别的(比如一个十字架)

当时就有点急,以为暴力硬上还是可以跑的,就写了个unordered_map硬算\(4n\)的状态,最后发现极限数据要跑8s

后面大力卡常一波,手写哈希表后喜提1700ms,然后就在我咒骂出题人卡常中比赛就结束了

赛后祁神发现其实对于同一片水域,其可以假设相机的空地也可能构成若干个连通块

而同一个连通块中的空地数量显然是\(2n\)级别的,根据这个来处理即可,最后15ms跑得飞快

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<assert.h>
#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=15,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m,ans,idx,tot; char a[N][N]; vector <pi> space;
int id[N][N],bel[N][N],num[N][N],vis[N][N],f[(1<<20)+5],clk[(1<<20)+5],rmv[N<<1];
inline void DFS1(CI x,CI y)
{
	id[x][y]=idx; for (RI i=0;i<4;++i)
	{
		int nx=x+dx[i],ny=y+dy[i];
		if (nx<1||nx>n||ny<1||ny>n) continue;
		if (a[nx][ny]=='*'&&!id[nx][ny]) DFS1(nx,ny);
	}
}
inline void DFS2(CI x,CI y)
{
	vis[x][y]=tot; space.push_back(pi(x,y));
	for (RI i=0;i<4;++i)
	{
		int nx=x+dx[i],ny=y+dy[i];
		if (nx<1||nx>n||ny<1||ny>n) continue;
		if (a[nx][ny]=='.'&&bel[nx][ny]==idx&&!vis[nx][ny]) DFS2(nx,ny);
	}
}
inline int SG(CI mask)
{
	if (mask==0) return 0;
	if (clk[mask]==tot) return f[mask];
	int vis[25]; memset(vis,0,sizeof(vis));
	for (RI i=0;i<m;++i) if ((mask>>i)&1) vis[min(m,SG(mask&rmv[i]))]=1;
	int mex=0; while (vis[mex]) ++mex;
	return clk[mask]=tot,f[mask]=mex;
}
int main()
{
	RI i,j,k; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%s",a[i]+1);
	for (i=1;i<=n;++i) for (j=1;j<=n;++j)
	if (a[i][j]=='*'&&!id[i][j])
	{
		++idx; DFS1(i,j);
		RI ii,jj; for (ii=1;ii<=n;++ii) for (jj=1;jj<=n;++jj)
		if (a[ii][jj]=='.')
		{
			bool flag=0; for (k=0;k<4&&!flag;++k)
			{
				int xx=ii+dx[k],yy=jj+dy[k];
				if (xx<1||xx>n||yy<1||yy>n) continue;
				if (a[xx][yy]=='*'&&id[xx][yy]==idx) flag=1;
			}
			if (flag) bel[ii][jj]=idx;
		}
		for (ii=1;ii<=n;++ii) for (jj=1;jj<=n;++jj)
		if (bel[ii][jj]==idx&&!vis[ii][jj])
		{
			++tot; space.clear(); DFS2(ii,jj);
			for (k=0;k<space.size();++k) num[space[k].fi][space[k].se]=k;
			for (k=0;k<space.size();++k)
			{
				rmv[k]=(1<<k);
				for (RI kk=0;kk<4;++kk)
				{
					int xx=space[k].fi+dx[kk],yy=space[k].se+dy[kk];
					if (xx<1||xx>n||yy<1||yy>n) continue;
					if (a[xx][yy]=='.'&&vis[xx][yy]==tot) rmv[k]|=(1<<num[xx][yy]);
				}
				rmv[k]=~rmv[k];
			}
			/*puts("---");
			printf("%d\n",space.size());
			for (auto [x,y]:space) printf("(%d,%d)\n",x,y);
			puts("---");*/
			m=space.size(); assert(m<=20); ans^=SG((1<<m)-1);
		}
	}
	//printf("SG = %d\n",ans);
	return puts(ans?"First player will win":"Second player will win"),0;
}

Postscript

今天晚上难得还有CF,而且是分场只能打Div1了,这下又要牢底坐穿了

posted @ 2024-01-27 18:57  空気力学の詩  阅读(58)  评论(0编辑  收藏  举报