总结:
这次做题状态不佳,签到十分钟,苦恼五小时。把签到题做完后,苦于F题,在想暴力做法,但是没做出来,看了视频讲解才知道是二次单调队列,还是需要补很多很多学习不深的点。做C题的时候,对树链的理解不深,理解有误,后来专门补了相关的知识。
B
题意
给出平面点坐标(不含原点),找到一个原点在边界上的圆,使尽可能多的点在这个圆上,求在圆边界上的点的数量最大值。点的坐标不重复,不含原点。
思路
所求圆边界上至少有两个点。根据三点共圆,所求圆必过原点O(0,0),枚举另外的两个点,计算出圆心坐标,统计相同的圆心坐标数量,圆心坐标相同数量越多,此时在题目所求圆的边界上的点越多。
三点共圆公式参考
https://www.cnblogs.com/xpvincent/p/8266734.html
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false),cin.tie(0);
#define ll long long
#define inf 0x3f3f3f3f
const int N=1e5+5;
//set<string>b;
//set<string>::iterator it;
struct node
{
ll x,y;
node(int a=0,int b=0):x(a),y(b){}
}a[N];
int ans=0;
double sq(ll x,ll y)
{
return x*x+y*y;
}
map< pair<double,double>,int>p;
void fun(node a,node b,node c)
{
ll ta=sq(a.x,a.y),tb=sq(b.x,b.y),tc=sq(c.x,c.y);
double A=a.x*(b.y-c.y)-a.y*(b.x-c.x)+b.x*c.y-c.x*b.y;
double B=ta*(b.y-c.y)+tb*(c.y-a.y)+tc*(a.y-b.y);
double C=ta*(c.x-b.x)+tb*(a.x-c.x)+tc*(b.x-a.x);
double x=-B/(2*A),y=-C/(2*A);
p[make_pair(x,y)]++;
if(p[make_pair(x,y)]>ans) ans=p[make_pair(x,y)];
}
int main()
{
IO;
int n,i,j;
node O=node(0,0);
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i].x>>a[i].y;
}
for(i=1;i<=n;i++)
{
p.clear();
for(j=i+1;j<=n;j++)
{
if(a[i].x*a[j].y==a[i].y*a[j].x) continue;
fun(O,a[i],a[j]);
}
}
cout<<ans+1<<endl;
return 0;
}
C
题意
给你一颗无根树,让你求最少的树链,使得每条边都被至少一条树链经过,输出树链的条数和每条树链的两个端点,如果有多个答案可以输出任意一组解。
思路
很显然,这个题的解所选节点肯定是叶子节点,因为如果其不为叶子节点,则存在其的子节点所包含的边数大于当前节点。然后我们先求出所有的叶子节点,然后进行两两配对,最后如果还剩下一个数,随便和谁配对都行。至于配对的原则,一开始想的是直接找其兄弟节点,找到谁就和谁配对,结果发现不对。
如图,若选择{1,2},{3,4},则根节点的两个边未被选中。
故考虑让a[i]和a[i+(k+1)/2]匹配(其中,k为根节点个数),这样可以保证覆盖边数最多。
代码
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int N=2e5+5;
vector<int>e[N];
int a[N],k=0;
void dfs(int u,int fa)
{
for(auto v:e[u])
if(v!=fa)
dfs(v,u);
if(e[u].size()==1)
a[++k]=u;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1,v,u;i<=n-1;i++)
{
scanf("%d %d",&u,&v);
e[u].pb(v);
e[v].pb(u);
}
int root=1;
dfs(root,0);
int mid=(k+1)/2;
vector<pii>v;
for(int i=1;i<=k-mid;i++)
v.pb(mp(a[i],a[i+mid]));
if(k&1)
v.pb(mp(a[mid],root));
cout<<v.size()<<endl;
for(auto i:v)
cout<<i.first<<' '<<i.second<<endl;
return 0;
}
F
题意
输入三个整数m, n, k构建一个m × n的矩阵A,A[m][n]所存的数为m,n的最大公倍数,求矩阵A中所有k × k的子矩阵中的最大值的和。
思路
两次单调队列的解法,思路为先用一个一维队列找出每一行长度为k区间的最大值,在通过另一个二维的队列找出列的最大值,最后求和即为答案。
代码
#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
typedef long long ll;
int findtheans(int A,int B)
{
int muli=A*B;
while(B)
{
int tmp=A%B;
A=B;
B=tmp;
}
return muli/A;
}
int n, m, k;
int A[5005][5005], tar[5005], a[5005][5005];
int main()
{
cin>>n>>m>>k;
int i,j;
ll ans = 0;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
A[i][j]=findtheans(i,j);
}
}
for(i=1;i<=n;i++)
{
int l=0,r=0;
for(j=1;j<=m;j++)
{
while(l<r&&j-tar[l]+1>k)
l ++;
while(l<r&&A[i][tar[r-1]]<=A[i][j])
r --;
tar[r++]=j;
a[i][j]=A[i][tar[l]];
}
}
for(int j=1;j<=m;j++)
{
int l=0,r=0;
for(int i=1;i<=n;i++)
{
while(l<r&&i-tar[l]+1>k)
l++;
while(l<r&&a[tar[r-1]][j]<=a[i][j])
r--;
tar[r++]=i;
a[i][j]=a[tar[l]][j];
}
}
for(int i=k;i<=n;i++)
{
for(int j=k;j<=m;j++)
{
ans+=a[i][j];
}
}
cout <<ans;
return 0;
}
G
题意
给出a、b两个串,长分别为n,m,求a中有多少长为m的连续子串t,使得ti>=bi
思路
对于每一个 Ai 需要求一个长度为 m 的 bitset Si,发现,在对 B 序列排序后,如果求出 B 对应的 bitset Sj 最后在Si 中二分找到 A[i] 在 B 中的位置即可。
代码
#include<bits/stdc++. h>
using namespace std;
typedef long long LL;
const int Inf=0x3f3f3f3f;
const double eps=1e-7;
const int maxn=2e5+50;
const int maxm=4e4+50;
bitset<maxm> cur,S[maxm];
vector< pair<int,int> > vc;
int a[maxn], b[maxm];
int n,m;
int find(int x){
if(x < vc[0].first) return 0;
int l=0,r=m-1, ret=0;
while (l<=r){
int mid=(l+r)>>1;
if(vc[mid].first <= x){
ret = mid;
l = mid+1;
}
else
r = mid-1;
}
return ret+1;
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<=m; i++){
scanf("%d",&b[i]);
vc.push_back({b[i], i});
}
sort(vc.begin(), vc.end());
for(int i=0; i<vc.size(); i++){
S[i+1] = S[i];
S[i+1].set( vc[i].second );
}
for(int i=n; i>n-m+1; i--){
cur = cur>>1;
cur.set(m);
cur = cur&S[find(a[i])];
// printf("(%d\n",find(a[i]));
}
int ans=0;
for(int i=n-m+1; i>=1; i--){
cur = cur>>1;
cur.set(m);
cur = cur&S[find(a[i])];
ans += cur[1];
// cout<<" [ "<<cur<<endl;
}
cout<<ans<<endl;
}
J
题意
给一个排列A={1,2,3,4,5…n },将它进行依照P序列k次置换,置换后变成排列B。
补充:这里置换的概念是将第i位的元素替换到第j位,被替换的位置j必须与其他位替换(不是原本理解的双方交换而是单方面的移动)。
思路
理解了置换后,很容易想到这个置换必须由若干个简单环构成,否则必然存在一个/多个元素丢失,不符合题意,所以我们可以用dfs求环的大小,假设环的大小为len,那么转换k次就相当于转换了k%len次,我们不妨设t=k%len。假设我现在进行了x次操作,那么如果刚好是(xt)%len==1即xk%len==1,求出逆元x即符合题意,构造时,如果b数组表示答案,c为环中元素,那么就有b[c[i]]=c[(i+x)%len]。
参考博客:https://www.cnblogs.com/whitelily/p/13296935.html
代码
#include<iostream>
#include<vector>
#define ll long long
using namespace std;
int n,k,to[100007],vis[100007],ans[100007];
int a[100007];
vector<int>ho;
void dfs(int p){ //求环
vis[p]=1;
ho.push_back(a[p]);
if(vis[to[p]]==0){
dfs(to[p]);
}
}
void go(){
int len=ho.size(),inv;
for(int i=0;i<len;i++){ //遍历求逆元
if((ll)i*k%len==1){
inv=i;
break;
}
}
for(int i=0;i<len;i++){ //按照 b[c[i]]=c[(i+x)%len]对答案进行构造
ans[ho[i]]=ho[(i+inv)%len];
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
to[i]=a[i];
}
for(int i=1;i<=n;i++){
ho.clear(); //每次在执行前都要把存环的vector清空
if(vis[i]==0){
dfs(i);
go();
}
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}puts("");
}