赛时解决题目:C,H,G,I
补题:A,F
G:
题意:给马和将军的坐标,然后马需要喝水,在x轴和y轴有河流,问马喝完水回来的最短路径。
题解:挺明显,数学中的镜像对称一下就可以了,让马或将军的坐标对x轴和y轴算出对称点再计算距离取min得到答案。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e5+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
double deal(ll x,ll y,ll tx,ll ty)
{
return sqrtl((y-ty)*(y-ty)+(x-tx)*(x-tx));
}
void solve()
{
double x,y;
double tx,ty;cin>>x>>y>>tx>>ty;
double nx=x,ny=y;
double ans=min(deal(x,y,-tx,ty),deal(x,y,tx,-ty));
cout<<fixed<<setprecision(14)<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。
I:
题意:给一堆朋友,问有多少个区间,彼此与彼此之间都是朋友。
题解:通过题目我们可以知道,你是我朋友,那我一定是你朋友(废话)。那么对于一个新加入的人来说,他必须是我们共同的朋友。我觉得我们可以很容易的想到,如果一个区间l~r内,如果存在一个人i和其他人不是朋友,那么这个区间肯定不是合法区间(好像也是废话)。
解题思路:双指针加二分,很明显我们不能遍历所有区间,但为什么我会想到双指针呢,因为根据我上面讲到的性质:如果一个区间l~r内,如果存在一个人i和其他人不是朋友,那么这个区间肯定不是合法区间。可以想到应该枚举右端点,然后左端点的范围肯定要大于i,不然就是我所提到的不合法区间。
那么二分又从何谈起呢,我们首先开个vector<ll>a[n+10],那么,我们在枚举右端点的时候,判断加入我们所维护的区间是否合理的条件就是,新加入的它,是否和原本的区间都是朋友,怎么判断呢,就是通过二分他的朋友,假设当前枚举到了r,那么我需要从a[r]中二分出r本身的位置,然后再二分出上一次得到的左端点的位置l在a[r]中的位置,很明显,如果r-l==位置(r)-位置(l),那么说明这是一个合法区间,否则我们需要让左端点进行右移或者让位置(l)右移,来得到新的好朋友区间,对于这个区间的权值,我们肯定要加上他所有的情况,也就是他左端点的情况数,因为右端点已经定下了,所以左端点情况数就是r-l,我单独将一个人的情况挑了出来,具体见代码。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
ll c[N];
void solve()
{
ll n,m;
cin>>n>>m;
vector<ll>a[n+10];
for(int i=1; i<=m; i++)
{
ll x,y;
cin>>x>>y;
a[x].pb(y);
a[y].pb(x);
}
for(int i=1; i<=n; i++)
{
a[i].pb(i);a[i].pb(n*2),a[i].pb(0);
sort(a[i].begin(),a[i].end());
c[i]=a[i].size();
}
ll nowl=1;
ll ans=0;
for(ll nowr=1; nowr<=n; nowr++)
{
ll l=1,r=c[nowr]-2;
while(l<r)
{
ll mid=l+r>>1;
if(a[nowr][mid]>=nowr)r=mid;
else l=mid+1;
}
ll R=l;
l=1,r=c[nowr]-2;
while(l<r)
{
ll mid=l+r>>1;
if(a[nowr][mid]>=nowl)r=mid;
else l=mid+1;
}
while(a[nowr][l]!=nowl||R-l!=nowr-nowl)
{
if(a[nowr][l]<nowl&&l<c[nowr]-2)l++;
else nowl++;
if(nowl==nowr)break;
}
// cout<<nowl<<' '<<nowr<<'\n';
ans+=nowr-nowl+1;
}
cout<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。
C:
题意:给一个乱序的排列(1~n只出现一次),问一次选四个数,然后这四个数乱排,问最少多少次能排成不升序。
题解:这道题一开始确实没有想法,但是实际操作的时候,我们肯定希望,数字a[i]能换到他想要的位置去,这时候再去找到他想要的那个位置,发现那个位置上也有个不对应的数字,这样继续找下去,会发现,形成了一个闭环,没错,这些数字形成了一个又一个的闭环块。对于一个闭环块,我们对他进行观察会发现,他就是一个很规律的数组,比如1->2(1)->3(2),()表示这个位置本来应该谁在上面,每个数字的合理位置很明显就是在下一个,动手操作一下发现,你最多一次性排好三个,那么你的ans对于这个块,所加上的权值就是(i+1)/3,(这里把i%3==2的情况算了一个1),然后记录一下i%3==2的情况数(后期要减)。为什么呢,因为对于i%3==0||i%3==1的情况,第一种情况你排完了很合理,第二种你排完剩一个,那么它肯定在它自己合法的位置上,对于i%3==2的情况,就是剩下两个需要置换的,那么很明显,置换只需要选2个,但我们能一次性选4个,所以后期答案需要减去cnt_2>>1。.输出ans得到结果。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
ll f[N];
ll now[N];
ll find(ll x)
{
if(f[x]==x)return x;
else return f[x]=find(f[x]);
}
void solve()
{
ll n;
cin>>n;
vector<ll>a(n+1);
for(int i=1; i<=n; i++)f[i]=i,now[i]=1,cin>>a[i];;
for(int i=1; i<=n; i++)
{
if(find(i)!=find(a[i]))///归块
{
now[find(i)]+=now[find(a[i])];
f[a[i]]=find(i);
}
}
vector<ll>v;
for(int i=1; i<=n; i++)
{
if(f[i]==i)
{
v.pb(now[i]);
}
}
ll ans=0;
ll cnt2=0;
for(auto i:v)
{
ans+=i/3;
if(i%3==2)ans--;
}
// ans+=cnt2/4;
cout<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。
H:
题意:给个数组,然后可以这么操作,下图(不想叭叭了)
题解:思考一下这两个操作,你会发现,不就是定个水平线然后翻面嘛,而且还是翻半面,因为全面翻的话就没意义了,手搓一下,发现跟上一场那个拖拉机(小车)撞墙很像,就会发现使用特定的水平线就可以精准控制最外面那个数字往里靠,思考一下,这不和小车撞墙一样拿gcd算一下就可以了,然后就搓出来了(其实就是辗转相减法)。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
void solve()
{
ll n;cin>>n;
set<ll> st;
for(int i=1;i<=n;i++)
{
ll x;cin>>x;
st.insert(x);
}
if(st.size()<=1)
{
cout<<0<<'\n';
return;
}
else
{
ll ans=0;
vector<ll>a(st.size(),0);
ll cnt=0;
for(auto i:st)a[cnt++]=i;
ll l=0;
ans=a[1]-a[0];
for(int i=2;i<cnt;i++)
ans=gcd(ans,a[i]-a[i-1]);
cout<<ans<<'\n';
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。
A:带权并查集
题意:给一棵树,然后一边给边,一边给查询的点,问以这个点为根,得到的最深度(就是以它为根的树的深度)。
解题思路:
有几个关键信息:
- 最后会构成一棵有根树。
- 所给的边是有向边
- 求的是树的深度
解题:
看起来像是需要动态维护这棵树的深度,但后期观察好像没有必要这么做(题目还有内存限制但奈何我看不懂一点,嘿嘿,但是我发现我数组开把N开到5e6就内存超限了)我们可以发现,对于一棵树的深度,我们完全可以用dfs先跑一遍得到它每个节点的深度,而且题目给的根是固定的。
然后我们会发现,如果想计算以其中一个节点作为根,想计算这棵树的深度的话,那么只需要找到这棵树的最深的节点就好了(因为他不能往他父亲那跑,因为有向边)。
我们首先先用dfs把这棵树所有节点的深度求出来,再搭配并查集维护加入边后每棵树的最深点即可。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
vector<ll>vec[N];
ll a[N],b[N],w[N];
bool vis[N];
ll deep[N],fa[N];
ll find(ll x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void dfs(ll u,ll fa)
{
deep[u]=deep[fa]+1;
for(auto it:vec[u])
dfs(it,u);
}
void solve()
{
ll n;
cin>>n;
for(int i=1;i<=n;i++)
{
vec[i].clear();
vis[i]=0;///入度
fa[i]=i;
}
for(int i=1;i<n;i++)
{
cin>>a[i]>>b[i]>>w[i];
vec[a[i]].push_back(b[i]);
vis[b[i]]=1;///b接在a的屁股后面
}
ll root=0;
for(int i=1;i<=n;i++)
if(!vis[i])
root=i;///根节点,题目保证只有一个根
dfs(root,0);///计算深度
for(int i=1;i<n;i++)
{
ll x=find(fa[a[i]]);///找到父节点
ll y=find(fa[b[i]]);
ll ans1=y,ans2=x;
if(deep[x]>=deep[y])///x的深度大于y
{
ans1=x;
ans2=y;
}
///ans1是比较深的那个
fa[ans2]=fa[ans1];///浅的是深的儿子
cout<<abs(deep[find(w[i])]-deep[w[i]])<<' ';
}
cout<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。
(哔哔几句:)写题的时候就应该好好看题,我就是因为没完全看完题目在那瞎想所以才想的一片浆糊没出。
F:
解题:试一下发现没什么特殊结构,不如直接造直线实在,然后再用二分的方式去查找。(最后发现如果分出来的答案有问题,判断答案和原来查找的数奇偶看是否加一就行)
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
if(b) while((a%=b)&&(b%=a));
return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%p;
b>>=1;
a=a*a%p;
}
return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀
///秋雨
ll get(ll x)
{
if(x&1)
return (x-1)*(x-1)/4;///分别算中心点和边缘点的f再减就行
else
return x*x/4-x/2;
};
void solve()
{
ll x;cin>>x;
ll l=1,r=3e9;
while(l<r)
{
ll mid=l+r>>1;
if(get(mid)>=x)r=mid;
else l=mid+1;
}
if(l%2==0)
if(get(l)%2==x%2)cout<<l<<'\n';
else cout<<l+1<<'\n';
else
cout<<l<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
cin>>t;
while(t--)
{
solve();
}
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,
///而后,春天再度归来。