SWERC 2021-2022
A
题意:
给\(n\)个物品,每个物品有个种类\(1\leq d_i\leq 10\)和价值\(b_i\),问是不是每种物品都出现过,如果出现过在每种物品种选一个使得价值和最大。
题解:
签到
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n;
int a[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=10;++i) a[i]=0;
for(int i=1;i<=n;++i)
{
int x,y;cin>>x>>y;
a[y]=max(a[y],x);
}
bool flag=0;
int sum=0;
for(int i=1;i<=10;++i)
{
if(a[i]==0) flag=1;
sum+=a[i];
}
if(flag) cout<<"MOREPROBLEMS\n";
else cout<<sum<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
D
题意:
给定两个字符串\(a,b\),仅由\(A,B,C\)构成。
可以在\(a\)串中任意位置添加或删除\(AA、BB、CC、ABAB、BCBC\)
问\(a\)串能不能变成\(b\)串
\(|a,b|\leq 200,t\leq 100\)
题解:
考虑在\(a,b\)串中一起删除子串,看最后两个字符串是不是能变成一样。
首先,任何操作都不能改变字符串任意字符的奇偶性,所以如果两个字符串的奇偶性不同,那肯定不能变成。
其次,一个\(BA\)串可以通过:
翻转过来,类似的\(AB\)也可以翻转过来,\(BC\)和\(CB\)也可以互相翻转。
所以可以看作\(AB、BC\)之间可以互相交换位置,而\(AC\)不行。
那可以都把\(B\)放到字符串的最后去,如果两个字符串中\(B\)中出现的奇偶性相同,则\(B\)对答案无影响,可以直接忽略。
剩下的就是\(A\)和\(C\),他们之间的相对位置不能改变,所以剩下的部分的相对位置就决定两个字符串能不能互相转化。
对于剩下的,如果满足有连续两个相同的字母,可以直接删去,留下类似\(ACACAC…\)的串。
如果\(a\)和\(b\)最后留下的串相同,就可以互相转化,否则不行。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m,num1,num2;
char a[N],b[N];
bool dp[N];
char s1[N],s2[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>(a+1)>>(b+1);
n=strlen(a+1);
m=strlen(b+1);
num1=num2=0;
int sum1=0,sum2=0;
for(int i=1;i<=n;++i)
{
if(a[i]=='B') {sum1^=1;continue;}
s1[++num1]=a[i];
while(num1>1&&s1[num1]==s1[num1-1]) num1-=2;
}
for(int i=1;i<=m;++i)
{
if(b[i]=='B') {sum2^=1;continue;}
s2[++num2]=b[i];
while(num2>1&&s2[num2]==s2[num2-1]) num2-=2;
}
bool flag=1;
if(num1!=num2||sum1!=sum2) flag=0;
for(int i=1;i<=min(num1,num2);++i) if(s1[i]!=s2[i]) flag=0;
if(flag) cout<<"YES\n";
else cout<<"NO\n";
}
}
}
signed main()
{
red::main();
return 0;
}
/*
*/
F
题意:
给定一数列\(a\),如果\(|i-j|\leq min(a_i,a_j)\)则两个位置能花\(1s\)互相通信。
现在\(s\)想给\(t\)传消息,最少花多长时间?
\(n\leq 1e6,a_i\leq n\)
题解:
考虑\(bfs\),但是边数可能是\(n^2\)级别的,一个优化思路是根据\(bfs\)的特点,当进入到一个节点后,这个节点的最短距离已经定好了,我们希望把其他指向这个节点的边都去掉。
但是暴力去掉也是\(O(n)\),最后还是\(O(n^2)\)
对于一个位置\(i\),设\(l_i=max(1,i-a_i),r_i=min(n,i+a_i)\)
如果有\(j<i\)且\(l_i\leq j,r_j\geq i\),那么\(i\)就可以向\(j\)传递消息。
这个可以用线段树来维护,每次找到\(r_j\)最大的位置。
每次找一个\(j\),直到\(r_j<i\)为止。
然后我们从\(i\)向\(j\)传递消息之后,等于是在\(bfs\)中已经走到\(j\)了,怎么把\(j\)的入边全部删掉呢?
只要让\(l_j=inf,r_j=-inf\),这样任何其他节点都不会再向\(j\)传递消息了。因为不存在其他节点\(k\)满足\(r_j\geq k\)了。
复杂度\(O(nlog n)\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,st,ed;
int a[N];
int dp[N];
typedef pair<int,int> pr;
struct segment_tree1
{
int ans[N<<2],ret[N<<2];
inline void build(int l,int r,int p)
{
ans[p]=inf;
if(l==r)
{
ret[p]=l;
return;
}
build(l,mid,ls(p));
build(mid+1,r,rs(p));
}
inline void update(int pos,int l,int r,int p,int k)
{
if(l==r)
{
ans[p]=k;
return;
}
if(pos<=mid) update(pos,l,mid,ls(p),k);
if(pos>mid) update(pos,mid+1,r,rs(p),k);
if(ans[ls(p)]<=ans[rs(p)]) ret[p]=ret[ls(p)],ans[p]=ans[ls(p)];
else ret[p]=ret[rs(p)],ans[p]=ans[rs(p)];
}
inline pr query(int tl,int tr,int l,int r,int p)
{
if(tl<=l&&r<=tr)
{
return pr(ans[p],ret[p]);
}
if(tr<=mid) return query(tl,tr,l,mid,ls(p));
if(tl>mid) return query(tl,tr,mid+1,r,rs(p));
pr t1=query(tl,tr,l,mid,ls(p)),t2=query(tl,tr,mid+1,r,rs(p));
if(t1.first<=t2.first) return t1;
return t2;
}
}T1;
struct segment_tree2
{
int ans[N<<2],ret[N<<2];
inline void build(int l,int r,int p)
{
ans[p]=-inf;
if(l==r)
{
ret[p]=l;
return;
}
build(l,mid,ls(p));
build(mid+1,r,rs(p));
}
inline void update(int pos,int l,int r,int p,int k)
{
if(l==r)
{
ans[p]=k;
return;
}
if(pos<=mid) update(pos,l,mid,ls(p),k);
if(pos>mid) update(pos,mid+1,r,rs(p),k);
if(ans[ls(p)]>=ans[rs(p)]) ret[p]=ret[ls(p)],ans[p]=ans[ls(p)];
else ret[p]=ret[rs(p)],ans[p]=ans[rs(p)];
}
inline pr query(int tl,int tr,int l,int r,int p)
{
if(tl<=l&&r<=tr)
{
return pr(ans[p],ret[p]);
}
if(tr<=mid) return query(tl,tr,l,mid,ls(p));
if(tl>mid) return query(tl,tr,mid+1,r,rs(p));
pr t1=query(tl,tr,l,mid,ls(p)),t2=query(tl,tr,mid+1,r,rs(p));
if(t1.first>=t2.first) return t1;
return t2;
}
}T2;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>st>>ed;
T1.build(1,n,1);
T2.build(1,n,1);
for(int i=1;i<=n;++i)
{
cin>>a[i];
T1.update(i,1,n,1,i-a[i]);
T2.update(i,1,n,1,i+a[i]);
dp[i]=0;
}
queue<int> q;
q.push(st);
dp[st]=0;
T1.update(st,1,n,1,inf);
T2.update(st,1,n,1,-inf);
while(!q.empty())
{
int i=q.front();q.pop();
while("miao")
{
pr tmp=T2.query(max(1ll,i-a[i]),i,1,n,1);
if(tmp.first<i) break;
int j=tmp.second;
dp[j]=dp[i]+1;
q.push(j);
T1.update(j,1,n,1,inf);
T2.update(j,1,n,1,-inf);
}
while("miao")
{
pr tmp=T1.query(i,min(n,i+a[i]),1,n,1);
if(tmp.first>i) break;
int j=tmp.second;
dp[j]=dp[i]+1;
q.push(j);
T1.update(j,1,n,1,inf);
T2.update(j,1,n,1,-inf);
}
}
cout<<dp[ed]<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
还有思路\(2\):
考虑施加一个约束:每次只能从\(j\)走到\(i(j<i)\)
每次只要找满足要求的\(j\):\(j<i\)且\(l_i\leq j,r_j\geq i\)
然后\(dp[i]=min\{dp[j]\}+1\)
我们可以把满足要求的\(j\)的线段树上的位置\(ans\)设为正确值,超过\(r_j\)表示范围之后设为\(inf\)
然后再反向跑一遍。
这样考虑有\(a<b<c<d\),我们从\(b->c->a->d\),那么一定有:
如果\(|c-b|=x\)
则\(|b-a|>x\)
则\(|d-c|>2x\)
距离起点至少翻了\(3\)倍。
也就是最多\(log\)次就能得到所有点的最短路。
只要循环到所有点的最短距离都不再变化为止。
复杂度\(O(nlog^2n)\)
G
题意:
给一颗\(n\)个节点的树,把\(1\sim n\)这\(n\)个数填入\(n\)个节点,让树上数字连续增大的路径条数最多。
题解:
先考虑一对相邻的节点\(a,b\),如果\(num_a<num_b\),则通过这对这节点的路径都是从\(a\)走向\(b\)的,反之都是从\(b\)走到\(a\)的
那么问题能否变成给树上的每一条边确定一个方向,让树上不同的有向路径条数最多。
可以,考虑任意一种边的定向,它一定是\(DAG,\)我们根据它的拓扑排序可以确定填数的方案。
下面考虑最优化方案数:
如果存在一条长度大于等于\(4\)的链,满足
那么翻转以\(a\)为根的子树和\(a->b\)的边,或者以\(c\)为根的子树和\(c->d\)的边会让答案更好。
证明:假如翻转了前者。
\(a\)的非\(b\)子树内部的路径数不变。
设\(A_{in}\)是终点为\(a\)的路径数。
\(B_{in}\)是终点为\(b\)的路径数。
\(B_{out}\)是起点为\(b\)的路径数。
翻转后\(\Delta_1 =A(B_{in}-B_{out})\)
同理,如果对\(c\)操作,则\(\Delta_2=D(C_{in}-C_{out})\)
因为存在\(c->……->b\)这条路径,所以\(C_{out}>B_{out}\)
同理\(B_{in}>C_{in}\)
\((B_{in}-B_{out})+(C_{in}-C_{out})\geq 2\)
那么要么\(B_{in}>B_{out}\),要么\(C_{in}>C_{out}\)
所以\(\Delta_1\)和\(\Delta_2\)中至少有一个\(>0\)
而现在,以任意一个节点为根的最优方案,都满足根的儿子们的子树中,最多只有一个儿子的子树中的一条边与其他边方向不同。
否则就可以通过上面的理论改变某条边的方向让路径数更多。
而且别的子树的方向必须与这棵子树的方向相反,否则又可以通过上面的理论翻转。
这时不妨设这个冲突点为根,那么根的所有儿子的子树中的边都是同一方向。
当某个节点为根,子树中的边都是同一方向时,路径数是\(\sum_{i=1}^{n}str[i]\),即所有节点子树和,加上跨过根的路径数。
设向上的路径数是\(S\),向下的是\(T\),且\(S+T=n-1\),跨过根的路径数是\(S*T\)。那么应该尽量让二者相当。
如果这个根存在一个子树,节点数量大于等于\(\frac{n}{2}\),那么其他所有子树都应该和这颗子树的边方向相反。
否则,我们就要用一个背包来算了!
好在,如果不存在,那这个节点就是重心,一棵树最多有两个重心。
我们记录\(cnt[i]\)为大小为\(i\)的子树有多少个,然后用多重背包的优化技巧,配合\(bitset\),而且最多有根号种不同的大小。
配合二进制是\(O(\frac{n\sqrt{n}*logn}{64})\),单调队列是\(O(\frac{n\sqrt{n}}{64})\)
其中最优答案一定在重心处取到:
假如现在根是\(rt\),\(rt\)不是重心。
最大的子树大小是\(X\),设最大子树中边的方向远离根,其他子树种边的方向指向根。
向这颗子树挪一步后,最优方案下远离根的数量变为\(Y\)
那么因为挪动导致子树内部路径减少数是\(X-Y\)
而跨过根节点的路径增加\(Y*X\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=1e6+10,mod=998244353,inf=2e9;
int n,rt,ans;
vector<int> eg[N];
int str[N];
int cnt[N];
inline void dfs(int now,int fa)
{
str[now]=1;
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
str[now]+=str[t];
}
}
int st[N],top;
inline void find(int now,int fa)
{
bool flag=0;
if((n-str[now])*2>n) flag=1;
for(int t:eg[now])
{
if(t==fa) continue;
if(str[t]*2>n) flag=1;
find(t,now);
}
if(!flag) rt=now;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i)
{
int x;cin>>x;
eg[x].emplace_back(i);
eg[i].emplace_back(x);
}
dfs(1,0);
find(1,0);
dfs(rt,0);
bitset<1000000> b;
b[0]=1;
for(int t:eg[rt])
{
++cnt[str[t]];
}
for(int i=0;i<n;++i)
{
if(cnt[i]>0)
{
int k=1;
while(k<=cnt[i])
{
b|=b<<(i*k);
cnt[i]-=k;
k*=2;
}
if(cnt[i]>k)
{
b|=b<<(cnt[i]*i);
}
}
}
for(int i=0;i<n;++i)
{
if(b[i]) ans=max(ans,i*(n-i-1));
}
ans+=accumulate(str+1,str+n+1,0ll);
cout<<ans<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
*/
H
队友会了就是我会了
I
题意:
有\(n\)个住户,第\(i\)个住户里有\(a_i\)个人,位于\((i-1)*100\)。
有\(m\)个冰淇淋店,第\(i\)个店位于\(p_i\)
你想开一个店,如果一个住户离你比离别的店更近,住户就会来你这,你最多能吸引多少人?
题解:
对每个住户,找到离他最近的店,比这个距离短的范围内都可以把\(a_i\)个人吸引到手,做个差分。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=2e6+10,mod=998244353,inf=2e9;
int n,m,num;
int a[N],s[N];
int p[N];
struct node
{
double pos;
int val;
inline bool operator < (const node &t) const
{
return pos<t.pos;
}
}c[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--)
{
int n,m;cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
for(int i=1;i<=m;++i)
{
cin>>p[i];
}
sort(p+1,p+m+1);
p[m+1]=inf;
for(int i=1;i<=n;++i)
{
int pos=(i-1)*100;
int t=lower_bound(p+1,p+m+1,pos)-p;
double len=p[t]-pos;
if(t>1) len=min(len,(double)(pos-p[t-1]));
c[++num]=(node){pos-len+0.01,a[i]};
c[++num]=(node){pos+len,-a[i]};
}
sort(c+1,c+num+1);
//for(int i=1;i<=num;++i) cout<<c[i].pos<<' '<<c[i].val<<"!!"<<endl;
int ans=0,sum=0;
for(int i=1;i<=num;++i)
{
int j=i;
while(c[j].pos==c[i].pos&&j<=num)
{
sum+=c[j].val;
++j;
}
--j;
i=j;
//cout<<c[i].pos<<"!!!"<<endl;
//cout<<sum<<' '<<j<<"!!"<<endl;
ans=max(ans,sum);
}
cout<<ans<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
3 3
5 10 12
300 50 50
*/
L
题意:
给定\(n\)个球,第\(i\)个球会在\(t_i\)秒出现在\(a_i\)位置,必须在\(t_i\)秒赶过去才能接住。
一开始第\(0\)秒你在\(0\)位置,最大速度是\(v\),问最多能接住多少球?
题解:
有朴素\(dp\):
考虑拆分绝对值符号
当\(a_i\geq a_j\)时
当\(a_i\leq a_j\)时
设\(x_i=v*t_i-a_i,y_i=v*t_i+a_i\),则必须满足\(x_j\leq x_i,y_j\leq y_i\)
把绝对值符号恢复,就等于是要求两个条件必须同时满足,也就是选一个序列出来,必须\(x\)和\(y\)两个键值都不降。
以\(x\)排序,求\(y\)的最长不下降子序列。
提前把从起点到不了的顶点去掉。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m,num;
int tim[N],pos[N];
struct node
{
int x,y;
inline bool operator < (const node &t) const
{
if(x!=t.x) return x<t.x;
return y<t.y;
}
}a[N];
int dp[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--)
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>tim[i];
}
for(int i=1;i<=n;++i)
{
cin>>pos[i];
if(abs(pos[i])>m*tim[i]) continue;
++num;
a[num].x=tim[i]*m-pos[i];
a[num].y=tim[i]*m+pos[i];
}
sort(a+1,a+num+1);
for(int i=1;i<=num;++i) dp[i]=inf*inf;
int ans=0;
for(int i=1;i<=num;++i)
{
if(a[i].x<0) continue;
int t=upper_bound(dp+1,dp+num+1,a[i].y)-dp;
ans=max(ans,t);
dp[t]=min(dp[t],a[i].y);
}
cout<<ans<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
M
题意:
有一排\(n\)瓶酒,每瓶可能红酒也可能是蓝酒。
有\(m\)个人,每个表示自己选择了某个子区间,喝到了\(a\)瓶红酒和\(b\)瓶蓝酒。
能否构造一个满足所有人要求的排列?
\(n,a,b\leq 100\)
题解:
找到\(a_i\)的最大值,\(b_i\)的最大值,如果加起来超过\(n\)就无解。
否则左边放\(a\)瓶红酒,右边放\(n-a\)瓶蓝酒。
然后它们每个人喝了多少酒让它们从中间分界处自己挑。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int a[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>m;
int t1=0,t2=0;
for(int i=1;i<=m;++i)
{
int x,y;
cin>>x>>y;
t1=max(x,t1),t2=max(y,t2);
}
if(t1+t2>n) cout<<"IMPOSSIBLE\n";
else
{
t1=n-t2;
while(t1--) cout<<"R";
while(t2--) cout<<"W";
cout<<'\n';
}
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
N
题意:
\(1\sim n^2\)放在\(n*n\)的格子里,问有多少个矩形满足,四个顶点中较小的两个数字是一条边的两个顶点?
\(n\leq 1500\)
考虑从小到大加入数字。
我们考虑一个合法的矩形在加入时的情形:
我们在加入第二大和第三大的数字时,必然有一条边已经加入数字了,而另一条边上的数字还没有。
而不合法的情况,因为先加入的是对角线上的数字,所以两条边上要么已经都有数字了,要么还都空着。
设\(u_i\)是第\(i\)行有多少已经加入的数字了,\(v_i\)是第\(i\)列有多少已经加入的数字了
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
const int N=3e5+10,mod=998244353,inf=2e9;
int n,m;
int a[1510];
int b[1510];
struct node
{
int x,y,v;
inline bool operator < (const node &t) const
{
return v<t.v;
}
}q[1500*1500+10];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
int x;cin>>x;
q[++m]=(node){i,j,x};
}
}
sort(q+1,q+m+1);
int ans=0;
for(int i=1;i<=m;++i)
{
ans+=a[q[i].x]*(n-1-b[q[i].y])+b[q[i].y]*(n-1-a[q[i].x]);
++a[q[i].x],++b[q[i].y];
}
cout<<ans/2<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
1
5
1 2
2 3
3 4
3 5
*/
O
队友会了就是我会了