Codeforces Round 805 (Div. 3)
基本情况
A、B、C题秒了。
D题一开始读错题了,以为是DP,后面发现是简单贪心,拖了点时间才AC。
不过无所谓,因为E题没思路了。
但是总感觉 C 做的太不优雅。
C. Train and Queries
我的做法
就纯用STL无脑模拟。跑了\(701ms\)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<queue>
const int N = 2e5 + 10;
int a[N];
int n, k;
std::map<int, std::priority_queue <int,std::vector<int>,std::greater<int> > > tag1;
std::map<int, std::priority_queue <int> > tag2;
void solve(int l, int r)
{
if (tag1[l].empty() || tag2[r].empty()) std::cout << "NO\n";
else
{
if (tag1[l].top() < tag2[r].top()) std::cout << "YES\n";
else std::cout << "NO\n";
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int _;std::cin >> _;
while(_--)
{
std::cin >> n >> k;
tag1.clear();tag2.clear();
for (int i = 1; i <= n; i++) std::cin >> a[i], tag1[a[i]].push(i), tag2[a[i]].push(i);
int a, b;
while(k--)
{
std::cin >> a >> b;
solve(a, b);
}
}
return 0;
}
更优雅的
其实思想一样,只是本质上只要维护最先出现和最后出现的下标即可,我开两个优先队列显然是浪费,用两个数组维护就行了。
#include <bits/stdc++.h>
using namespace std;
int n,m,i,x,y;
void solve(){
cin>>n>>m;
map <int,int> a,b;
for (i=1;i<=n;i++){
cin>>x;
if (a[x]==0) a[x]=i;
b[x]=i;
}
for (i=1;i<=m;i++){
cin>>x>>y;
if (a[x]!=0 && b[y]!=0 && a[x]<=b[y]) cout<<"YES\n";
else cout<<"NO\n";
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int tests=1;
cin>>tests;
while (tests--)
solve();
return 0;
}
E. Split Into Two Sets
要用到拓展域并查集或者二分图,待我先去学学。
拓展域并查集解法
OK,周三学成归来。
那么这题确实可以用扩展域并查集来做。
因为依照题意,两个相同的数肯定不能在一起。
把一个数对的每个数构造出对应的敌人。
x_me = x, x_enemy = x + n;
y_me = y, y_enemy = y + n;
然后一个数与另一个数的敌人合并。如果最后检索到一个数自己和这个数的敌人的祖先相同说明还是有两个相同的数在一起了,无法分为两组。
当然,如果相同的数的数目比二还大,那肯定也无法分为两组。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using ll = long long;
const int N = 2e5 + 10;
int fa[N << 1];
int get(int x) {return fa[x] == x ? x : get(fa[x]);}
void merge(int x, int y)
{
int fx = get(x), fy = get(y);
if (fx == fy) return;
fa[fx] = fy;
}
void init(int n)
{
for (int i = 1; i <= (n << 1); i++) fa[i] = i;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int _, n;
std::cin >> _;
while(_--)
{
std::cin >> n;
std::vector<int> tag(n + 1);
init(n);
for (int i = 1; i <= n; i++)
{
int x, y;
std::cin >> x >> y;
tag[x]++;tag[y]++;
merge(x, y + n);merge(x + n, y);
}
bool ok = true;
for (int i = 1; i <= n; i++)
{
if (tag[i] > 2) ok = false;
if (get(i) == get(i + n)) ok = false;
}
std::cout << (ok ? "YES\n" : "NO\n");
}
return 0;
}
二分图判定解法
OK,周四学成归来。
思路
考虑将所有的不能放到一起的多米诺骨牌之间连上一条边,然后将整个图分成一个二分图就行了。
注意到这种方法可能会被有较多同样数字的多米诺骨牌的情况卡到 \(O(n^2)\) 条边,于是想到如果出现有三个或者更多的相同数字的情况时直接判断无解,正确性显然。
总复杂度 \(O(n)\)。
代码
#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=2000005;
int T,a[MAXN],b[MAXN],v[MAXN],flag,head[MAXN],num[MAXN*3],nxt[MAXN*3],tot,n,vis[MAXN];
void add(int x,int y){
nxt[++tot]=head[x];
head[x]=tot;
num[tot]=y;
}
void work(int num,int p){
if(v[num]==-1){flag=1;return;}
if(v[num]==0){v[num]=p;return;}
add(v[num],p);
add(p,v[num]);
v[num]=-1;
}
void dfs(int now,int p){
vis[now]=p;
for(int i=head[now];i;i=nxt[i]){
if(vis[num[i]]==p)flag=1;
if(vis[num[i]]==0)dfs(num[i],3-p);
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
head[i]=0,v[i]=0,vis[i]=0;
for(int i=1;i<=tot;i++)
num[i]=nxt[i]=0;
tot=0;flag=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
work(a[i],i);work(b[i],i);
if(a[i]==b[i])flag=1;
}
for(int i=1;i<=n;i++)
if(v[i]!=-1)flag=1;
for(int i=1;i<=n;i++)
if(vis[i]==0)dfs(i,1);
if(flag==1)printf("NO\n");
if(flag==0)printf("YES\n");
}
return 0;
}