The 3rd Universal Cup. Stage 18: Southeastern Europe
Preface
好经典的三个半小时 9 题下班啊
感觉复建了一段时间后写题的速度和准确度有所回暖,但不会做的题还是一点没招
A. All-Star
队友开局写的,我题目都没看
#include <bits/stdc++.h>
int n;
std::vector<int> out[1003];
int root;
void dfs(int cur, int pa) {
for(auto out: out[cur]) if(out != pa) {
std::cout << root << ' ' << cur << ' ' << out << char(10);
dfs(out, cur);
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n;
for(int i = 1, u, v; i < n; ++i) {
std::cin >> u >> v;
out[u].emplace_back(v);
out[v].emplace_back(u);
}
root = 1;
for(int i = 2; i <= n; ++i) if(out[i].size() > out[root].size()) root = i;
int ans = n - 1 - (int)out[root].size();
std::cout << ans << char(10);
for(auto out: out[root]) dfs(out, root);
return 0;
}
C. Duloc Network
考虑对于两个不交点集 \(A,B\),若 \(query(A)+query(B)=query(A\cup B)\),则说明两个点集间距离大于 \(2\);反之则说明两个点集联通(距离小于等于 \(2\))
动态维护集合 \(S\) 表示当前已知的联通的点集,如果用上面方法判断 \(S\) 与 \(V-S\) 不联通则已经得出答案,否则 \(V-S\) 中至少存在一个点与 \(S\) 联通
可以通过对 \(V-S\) 二分来找出对应的点,询问次数 \(2n\log n\le 3500\)
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=205;
int n;
inline int query(vector <int> vec)
{
static char s[N];
for (RI i=1;i<=n;++i) s[i]='0';
for (auto x:vec) s[x]='1';
printf("? ");
for (RI i=1;i<=n;++i) putchar(s[i]);
printf("\n"); fflush(stdout);
int d; scanf("%d",&d);
return d;
}
inline void answer(CI x)
{
printf("! %d\n",x); fflush(stdout);
}
int main()
{
scanf("%d",&n);
vector <int> IN={1},OUT;
for (RI i=2;i<=n;++i) OUT.push_back(i);
bool flag=1;
while ((int)IN.size()<n)
{
int fin=query(IN);
int l=0,r=(int)OUT.size()-1,res=-1;
while (l<=r)
{
int mid=l+r>>1;
vector <int> tmp;
for (RI i=0;i<=mid;++i) tmp.push_back(OUT[i]);
int fout=query(tmp);
for (auto x:IN) tmp.push_back(x);
int finout=query(tmp);
if (fin+fout!=finout) res=mid,r=mid-1; else l=mid+1;
}
if (res==-1) { flag=0; break; }
IN.push_back(OUT[res]);
OUT.erase(OUT.begin()+res);
}
answer(flag);
return 0;
}
D. Donkey and Puss in Boots
队友开局写的,我题都没看
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+5;
int n, A[N];
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
int sum = 0, mx = 0;
for (int i=1; i<=n; ++i) cin >> A[i], sum += A[i], mx = max(mx, A[i]);
if (0 == mx) cout << "Puss in Boots\n";
else if (sum - mx >= n) cout << "Puss in Boots\n";
else cout << "Donkey\n";
return 0;
}
F. Magical Bags
首先对于初始局面下每对背包之间的 goodness 是很好判断的:把每个背包中的最小最大值看作一个区间,若对应的区间有交则说明这对背包之间是 good 的
因此把问题转化到区间上来,显然每个背包只会留下 \(\le 2\) 个物品,不难发现以下两个性质:
- 若将某个区间删至孤点,设保留下的点权值为 \(x\),则 \(x\) 必须与所有本来和该区间相交的区间相交;
- 若决定删除某个区间,则所有与该区间相交的区间都不能删除;
对于性质一,要判断一个区间是否能删,需要维护出所有与它相交的区间的交,这是个经典问题,将所有区间按左端点排序后用 set 维护前面,ST 表维护后面即可
对于性质二,我们考虑 DP 维护,令 \(f_i\) 表示处理了前 \(i\) 个区间的答案,不难发现转移是个 \((r_i,f_i)\) 的二维数点,用树状数组维护即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=500005,INF=1e9;
int n,m,id[N],f[N]; pi itv[N]; vector <int> tv[N],v[N];
namespace ST
{
int mnr[N][20],_lg[N];
inline void init(void)
{
_lg[0]=-1;
for (RI i=1;i<=n;++i) _lg[i]=_lg[i>>1]+1;
for (RI i=1;i<=n;++i) mnr[i][0]=itv[i].second;
for (RI j=1;(1<<j)<=n;++j)
for (RI i=1;i+(1<<j)-1<=n;++i)
mnr[i][j]=min(mnr[i][j-1],mnr[i+(1<<j-1)][j-1]);
}
inline int query(CI l,CI r)
{
int k=_lg[r-l+1];
return min(mnr[l][k],mnr[r-(1<<k)+1][k]);
}
}
class Tree_Array
{
private:
int bit[N];
public:
#define lowbit(x) (x&-x)
inline void add(RI x,CI y)
{
for (;x<=m;x+=lowbit(x)) bit[x]=max(bit[x],y);
}
inline int get(RI x,int ret=0)
{
for (;x;x-=lowbit(x)) ret=max(ret,bit[x]); return ret;
}
#undef lowbit
}F;
int main()
{
scanf("%d",&n); int sum=0;
vector <int> rst;
for (RI i=1;i<=n;++i)
{
int k; scanf("%d",&k); m+=k;
tv[i].resize(k);
for (RI j=0;j<k;++j)
scanf("%d",&tv[i][j]),rst.push_back(tv[i][j]);
sort(tv[i].begin(),tv[i].end());
if (tv[i].size()==1) ++sum; else sum+=2;
}
sort(rst.begin(),rst.end());
for (RI i=1;i<=n;++i)
for (auto& x:tv[i]) x=lower_bound(rst.begin(),rst.end(),x)-rst.begin()+1;
for (RI i=1;i<=n;++i) id[i]=i;
auto cmp=[&](CI x,CI y)
{
return tv[x][0]<tv[y][0];
};
sort(id+1,id+n+1,cmp);
for (RI i=1;i<=n;++i)
v[i]=tv[id[i]],itv[i]={v[i][0],v[i].back()};
// for (RI i=1;i<=n;++i) printf("[%d,%d]\n",v[i][0],v[i].back());
ST::init(); set <int> Rpos; Rpos.insert(INF);
for (RI i=1;i<=n;++i)
{
int l=itv[i].first,r=itv[i].second,L=0,R=INF;
int pos=lower_bound(itv+i+1,itv+n+1,pi{r,r})-itv-1;
if (i+1<=pos) L=itv[pos].first,R=ST::query(i+1,pos);
R=min(R,*Rpos.upper_bound(l)); bool valid=0;
if (L<=R)
{
auto p=lower_bound(v[i].begin(),v[i].end(),L);
auto q=lower_bound(v[i].begin(),v[i].end(),R);
if (p!=q) valid=1;
}
if (v[i].size()==1) valid=0;
f[i]=max(f[i],f[i-1]);
if (valid) f[i]=max(f[i],F.get(l)+1);
Rpos.insert(r); F.add(r,f[i]);
}
return printf("%d",sum-f[n]),0;
}
G. Shrek's Song of the Swamp
队友开局写的,我题意都没看
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int lst[N], dp[N], mx[N];
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
map<int, int> mp;
for (int i=1; i<=n; ++i) {
int a; cin >> a;
lst[i] = mp[a];
mp[a] = i;
}
for (int i=1; i<=n; ++i) {
if (0 == lst[i]) dp[i] = 0;
else {
dp[i] = max(dp[lst[i]]+1, mx[lst[i]-1]+2);
}
mx[i] = max(mx[i-1], dp[i]);
}
int ans = 0;
for (int i=1; i<=n; ++i) {
ans = max(ans, dp[i]);
}
cout << ans << '\n';
return 0;
}
H. Shreckless
刚开始犯病了写了两个假做法导致这场开题顺序和罚时都炸了
不难发现这题本质要求就是找出 \(n\) 对不相交的逆序对,考虑一列一列从后往前贪心
每次用 two pointers 尽可能多地匹配相邻两列的逆序对,多出的留到下次一定更优
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m; vector <int> col[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&m);
for (RI j=1;j<=m;++j)
col[j].resize(n+1),col[j][0]=1e9;
for (RI i=1;i<=n;++i)
for (RI j=1;j<=m;++j)
scanf("%d",&col[j][i]);
for (RI j=1;j<=m;++j) sort(col[j].begin(),col[j].end(),greater <int>());
int cnt=0,lst=1;
for (RI j=m;j>1;--j)
{
for (RI i=1;i<=n;++i)
{
while (lst<=n&&col[j][lst]>=col[j-1][i]) ++lst;
if (lst<=n) ++cnt,++lst; else { lst=i; break; }
}
}
puts(cnt>=n?"YES":"NO");
}
return 0;
}
J. Make Swamp Great Again
哈哈又是队友写的,回头发现这场感觉我一点没出力啊
#include <bits/stdc++.h>
int n;
int t[100000];
struct info_t {
int count;
bool hikari;
info_t(): count(0), hikari(false) {}
};
std::map<int, info_t> ars;
inline int& get_t(int i) {
if(i < 0) i += n;
if(i >= n) i -= n;
return t[i];
}
int min3(int a, int b, int c) { return std::min(a, std::min(b, c)); }
int max3(int a, int b, int c) { return std::max(a, std::max(b, c)); }
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n;
for(int i = 0; i < n; ++i) std::cin >> t[i];
for(int i = 0; i < n; ++i) {
ars[t[i]].count += 1;
if(t[i] == min3(get_t(i - 2), get_t(i - 1), get_t(i))) ars[t[i]].hikari = true;
if(t[i] == max3(get_t(i - 2), get_t(i - 1), get_t(i))) ars[t[i]].hikari = true;
if(t[i] == min3(get_t(i - 1), get_t(i), get_t(i + 1))) ars[t[i]].hikari = true;
if(t[i] == max3(get_t(i - 1), get_t(i), get_t(i + 1))) ars[t[i]].hikari = true;
if(t[i] == min3(get_t(i), get_t(i + 1), get_t(i + 2))) ars[t[i]].hikari = true;
if(t[i] == max3(get_t(i), get_t(i + 1), get_t(i + 2))) ars[t[i]].hikari = true;
}
for(int i = 0; i < n; ++i) {
auto [count, hikari] = ars[t[i]];
std::cout << n - count + !hikari << char(i == n - 1 ? 10 : 32);
}
return 0;
}
K. Intrusive Donkey
考虑对于原序列第 \(i\) 个位置上的字符维护 \(a_i\) 表示其重复的次数,初始时所有 \(a_i=1\)
用线段树维护 \(\{a_i\}\),很容易发现不管是把修改的 \(l,r\) 映射到原字符,或者查询某个位置的字符都很容易用线段树上二分维护
而一次修改操作可以转化为 \(\{a_i\}\) 上的两次单点加与一次区间乘 \(2\),很容易直接维护
#include<cstdio>
#include<iostream>
#include<array>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,q; char s[N];
class Segment_Tree
{
private:
int sum[N<<2],tag[N<<2];
inline void apply(CI now,CI mv)
{
sum[now]*=mv; tag[now]*=mv;
}
inline void pushup(CI now)
{
sum[now]=sum[now<<1]+sum[now<<1|1];
}
inline void pushdown(CI now)
{
if (tag[now]!=1) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=1;
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void build(TN)
{
sum[now]=r-l+1; tag[now]=1; if (l==r) return;
int mid=l+r>>1; build(LS); build(RS);
}
inline void modify_add(CI pos,CI mv,TN)
{
if (l==r) return (void)(sum[now]+=mv);
int mid=l+r>>1; pushdown(now);
if (pos<=mid) modify_add(pos,mv,LS);
else modify_add(pos,mv,RS);
pushup(now);
}
inline void modify_mul(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return apply(now,2);
int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify_mul(beg,end,LS);
if (end>mid) modify_mul(beg,end,RS);
pushup(now);
}
inline array <int,3> query(CI val,TN)
{
if (l==r) return {l,val,sum[now]};
int mid=l+r>>1; pushdown(now);
if (sum[now<<1]>=val) return query(val,LS);
else return query(val-sum[now<<1],RS);
}
#undef TN
#undef LS
#undef RS
}SEG;
signed main()
{
scanf("%lld%lld%s",&n,&q,s+1);
for (SEG.build();q;--q)
{
int opt,l,r; scanf("%lld%lld",&opt,&l);
if (opt==1)
{
scanf("%lld",&r);
auto [p,a,vp]=SEG.query(l);
auto [q,b,vq]=SEG.query(r);
if (p==q) SEG.modify_add(p,b-a+1); else
{
SEG.modify_add(p,vp-a+1);
SEG.modify_add(q,b);
if (p+1<=q-1) SEG.modify_mul(p+1,q-1);
}
} else
{
auto [p,a,vp]=SEG.query(l);
printf("%c\n",s[p]);
}
}
return 0;
}
L. Ogre Sort
不难发现一次 \((x,y)\) 操作可以转化为 \(y\) 次 \((x,1)\) 操作,因此不妨钦定所有操作的 \(y=1\)
简单手玩一下会发现此时只要从大到小考虑每个数,用树状数组维护位置即可
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+5;
int n, A[N], pos[N];
struct Fenwick {
int fen[N];
int lb(int x) {return x&(-x);}
void upd(int p, int v) {
for (int i=p; i<=n; i+=lb(i)) fen[i] += v;
}
int qry(int p) {
int res = 0;
for (int i=p; i>0; i-=lb(i)) res += fen[i];
return res;
}
}fen;
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n; ++i) cin >> A[i], pos[A[i]]=i;
int xx = n;
for (int i=n; i>0; --i) {
fen.upd(i, 1);
if (A[i]==xx) --xx;
}
int frt = 0;
cout << xx << ' ' << xx << '\n';
for (int i=xx; i>0; --i) {
cout << fen.qry(pos[i]) + frt << ' ' << 1 << '\n';
++frt;
fen.upd(pos[i], -1);
}
return 0;
}
Postscript
B 题经典找性质 counting 啥都看不出,直接倒闭

浙公网安备 33010602011771号