B. Charming Meals(枚举)(*1486)
将a和b排序后固定a,对b错位相减,枚举所有错位的位置,统计这些情况下答案的最大值。时间复杂度为。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,a[5010],b[5010];
void solve()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
sort(a,a+n),sort(b,b+n);
ll ans=0;
for(int i=1;i<=n;i++)
{
ll res=1e18;
for(int j=0;j<n;j++) res=min(res,abs(a[j]-b[(j+i-1)%n]));
ans=max(ans,res);
}
cout<<ans<<endl;
}
int main()
{
cin>>t;
while(t--) solve();
}
I. Disks(二分图染色)(*1858)
判断两个圆相切的方法是两个圆圆心的距离等于两个圆的半径之和,建议不要用根号,不然可能有精度误差,可以将等式两边平方后判断。
首先如果有两个相邻的圆,其中一个半径减小,另一个的半径就必须增加。我们把每个圆看成图论中的节点,两圆相切看成连边,这样整个二维平面就被转化为了图论的几个连通集,然后对单个连通集考虑以下情况:
- 不含环:很明显这种情况要求集合大小为奇数,这样半径减小的圆就会比半径增大的圆多一个
- 含有奇数长度的环:按照相邻圆半径变化相反的规则,长度为奇数的环中必定有两个相切的圆的半径变化相同,这与前面的分析矛盾。因此连通块内不能存在奇数环。
- 含有偶数长度的环:偶数长度的环相当于抵消了半径的影响,对去除偶数环后的部分进行考虑即可。
我们可以发现,相切圆半径变化必须相反的规则类似于二分图中的染色,而且染色法可以轻松地判定奇数环的存在。因此,我们采用染色法来对每个连通块进行染色,判断每个连通块是否成功染色以及两种颜色的数量是否不同。由于n只有1000,我们可以使用很多复杂度为的算法来实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,st[1010],color[1010],p[1010];
vector<int>v[1010],ve[1010];
struct node{
ll x,y,r;
}a[1010];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool dfs(int u,int c)
{
color[u]=c;
for(int i=0;i<v[u].size();i++)
{
int j=v[u][i];
if(!color[j])
{
if(!dfs(j,3-c)) return false;
}
else if(color[j]==c) return false;
}
return true;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y>>a[i].r;
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y)==(a[i].r+a[j].r)*(a[i].r+a[j].r))
v[i].push_back(j),v[j].push_back(i),p[find(i)]=find(j);
}
}
for(int i=1;i<=n;i++) ve[find(i)].push_back(i);
for(int i=1;i<=n;i++)
{
if(!color[i])
{
if(dfs(i,1)==false) st[i]=0;
}
}
int judge=0;
for(int i=1;i<=n;i++)
{
if(!ve[i].size()) continue;
int x=0,y=0,flag=1;
for(auto j:ve[i])
{
for(auto k:v[j])
{
if(color[k]==color[j])
{
flag=0;
break;
}
}
if(!flag) break;
}
for(auto j:ve[i])
if(color[j]==1) x++;
else y++;
if(x==y||!flag) continue;
else
{
judge=1;
break;
}
}
if(judge) cout<<"YES\n";
else cout<<"NO\n";
}
C. Annual Ants' Gathering(拓扑排序)(*1872)
拓扑排序是本题的一个十分简便的做法。
对于两个相邻的节点,一定是子树小的往子树大的节点移动。由于无法确定终点,一个好的方法是让整棵树最外围的节点向内移动。考虑进行贪心策略,用优先队列对子树大小从小到大存放bfs时的节点,这样每次取出的点都是蚂蚁数量最少的,是目前的最优策略,如果在这种情况下都无法移动,那么说明不会再有其他策略能让蚂蚁汇聚,输出NO即可。如果成功完成了拓扑排序,那么蚂蚁就可以汇聚一点,bfs的最后一个点就是汇聚点。
时间复杂度为。
思路来源:qwqIdontHaveAgf
#include<bits/stdc++.h>
using namespace std;
int n,d[200010],sz[200010];
vector<int>v[200010];
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
d[x]++,d[y]++;
sz[x]=sz[y]=1;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
for(int i=1;i<=n;i++)
if(d[i]==1) q.push({1,i});
while(!q.empty())
{
auto t=q.top();
q.pop();
int u=t.second;
for(auto j:v[u])
{
if(d[j]<=1) continue;
if(sz[j]>=sz[u])
{
sz[j]+=sz[u];
d[j]--;
if(d[j]==1) q.push({sz[j],j});
}
else
{
cout<<"NO";
return 0;
}
}
}
cout<<"YES";
}
F. Dating(排序+集合)(*2277)
遍历所有用户,我们可以用last[i]表示活动i最近一次出现在哪个用户的集合里,我们令last[i]的初值为-1。考虑n个集合之间的关系:包含、相交与不相交。
- 不相交:如果该用户集合的每个元素的 last 值都是 -1 ,说明每个元素至今都未出现过,该用户集合不与之前的任何一个集合相交。
- 相交:如果在遍历某个用户的集合时,集合内的每个元素的 last 值并不完全相同,那么说明该集合中与之前的某些集合相交,该集合与前面的任何一个集合都是可能的解。
- 包含:在上面相交得到的可能解中,我们还需要排除包含的关系。比如用户1的集合为 [1,2] ,遍历完后 last[1] = last[2] = 1;之后用户 2 的集合为 [1,2,5] ,此时 last[1] = last[2] = 1 , last[5]=-1 ,满足相交条件,但同时也是包含关系,不符合题意。为了解决这个问题,我们先遍历大的集合,即将所有的用户按照集合大小排序,这样每次判断的就是被包含的关系。如上面的例子,排序后用户 1 变为 [1,2,5] ,last[1] = last[2] = last[5] = 1 ,用户 2 为 [1,2] ,遍历判断时 last[1] = last[2] = 1,这样就可以判掉这种情况了。
总时间复杂度为。
思路来源:BrotherCall
#include<bits/stdc++.h>
using namespace std;
int n,m,c,x,last[1000010];
struct node{
int len,idx;
vector<int>v;
}a[200010];
bool cmp(node A,node B)
{
return A.len>B.len;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>c;
a[i].len=c,a[i].idx=i;
for(int j=1;j<=c;j++)
{
cin>>x;
a[i].v.push_back(x);
}
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
int maxx=0,flag=0;
for(auto j:a[i].v) maxx=max(maxx,last[j]);
for(auto j:a[i].v)
if(last[j]!=maxx)
{
flag=1;
break;
}
if(flag)
{
cout<<"YES\n"<<a[i].idx<<" "<<a[maxx].idx<<endl;
return 0;
}
else
for(auto j:a[i].v) last[j]=i;
}
cout<<"NO";
}
G. Scooter(模拟)(*2327)
我们最好不要对已经匹配的教学楼进行变动,由于题目保证一定有解,因此起步最好是从不需要任何教授的教学楼开始接送,然后进行暴力查找匹配,将车上的教授与目的地的教授进行互换。如果一口气匹配到底最好;如果出现了送走教授后无法接到新的教授的情况,即原串当前位置为 '-' 的情况,那么就需要重新寻找起点并重复上面的步骤。同时注意如果接教授前看看场上还需不需要这个专业的教授,不需要就不要再接这个教授了,否则只会徒增麻烦。
用dfs实现就行了,时间复杂度为。
思路来源:qwqIdontHaveAgf
#include <bits/stdc++.h>
using namespace std;
const string a="DRIVE",b="PICKUP",c="DROPOFF";
vector<string>ans;
int n,f[2010];
string s,t;
void op1(int u)
{
ans.push_back(a+" "+to_string(u));
}
void op2()
{
ans.push_back(b);
}
void op3()
{
ans.push_back(c);
}
void dfs(int u)
{
int ne=-1,c=0,m=0;
for(int i=0;i<n;i++)
{
if(f[i]) continue;
if(s[i]!='-')
{
if(s[i]=='C') c++;
else if(s[i]=='M') m++;
}
}
for(int i=0;i<n;i++)
{
if(f[i]) continue;
if(s[i]==t[u]&&t[i]!='-')
{
if(s[i]=='C') c--;
else if(s[i]=='M') m--;
f[i]=1;
op1(i+1),op3();
if((t[i]=='M'&&m>0)||(t[i]=='C'&&c>0)) op2();
ne=i;
break;
}
}
if(ne==-1)
{
for(int i=0;i<n;i++)
{
if(f[i]) continue;
if(s[i]==t[u])
{
if(s[i]=='C') c--;
else if(s[i]=='M') m--;
f[i]=1;
op1(i+1),op3();
break;
}
}
if(m+c>=1)
{
for(int i=0;i<n;i++)
{
if(f[i]) continue;
if(s[i]=='-')
{
f[i]=1;
op1(i+1);
if((t[i]=='M'&&m>0)||(t[i]=='C'&&c>0)) op2();
ne=i;
break;
}
}
}
if(m+c>=1&&ne!=-1) dfs(ne);
}
else if(m+c>=1) dfs(ne);
else return;
}
int main()
{
cin>>n;
cin>>s>>t;
int c=0,m=0;
for(int i=0;i<n;i++)
{
f[i]=(s[i]==t[i]);
if(s[i]=='C') c++;
else if(s[i]=='M') m++;
}
if(c+m<1)
{
cout<<0<<endl;
return 0;
}
int st=-1;
for(int i=0;i<n;i++)
{
if(s[i]==t[i]) continue;
if(s[i]=='-'&&t[i]!='-')
{
f[i]=1;
st=i;
break;
}
}
if(st!=-1)
{
op1(st+1),op2();
dfs(st);
cout<<ans.size()<<endl;
for(auto i:ans) cout<<i<<endl;
}
else cout<<0<<endl;
}