测试「20201012测试总结」
T1T2分往脸上送,T3直接劝退。
T1
数三角形。
先固定一条直线,求它能与其他直线组成多少个三角形。这个很好求。
令 \(S\) 为所有直线的集合,\(k_i\) 表示直线 \(i\) 的斜率,\(c_k\) 表示斜率为 \(k\) 的直线条数,则直线 \(l\) 能与其他直线组成的三角形个数即为:
\[\begin{aligned}
\frac{1}{2}\sum_{i\in S,k_i\not =k_l}c_{k_i}\times (n-c_{k_l}-c_{k_i})&=\frac{1}{2}\sum_{i\in S,k_i\not =k_l}(c_{k_i}\times (n-c_{k_l})-c_{k_i}^2)\\
&=\frac{1}{2}((n-c_{k_l})\sum_{i\in S,k_i\not =k_l}c_{k_i}-\sum_{i\in S,k_i\not =k_l}c_{k_i}^2)\\
&=\frac{1}{2}((n-c_{k_l})^2-(\sum_{i\in S}c_{k_i}^2-c_{k_l}^2))
\end{aligned}
\]
将斜率离散化,预处理出 \(\sum_{i\in S}c_{k_i}^2\) ,枚举 \(l\) 。
因为最后每个三角形都会被计算 \(3\) 次,所以最后答案还要除以 \(3\) 。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=3e5+5;
typedef long long lxl;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
}
int n;
double k[maxn],b[maxn];
int a[maxn];
lxl cnt[maxn],sum;
int main()
{
// freopen("trokuti.in","r",stdin);
// freopen("trokuti.out","w",stdout);
read(n);
for(int i=1,A,B,C;i<=n;++i)
{
read(A),read(B),read(C);
if(!B) k[i]=b[i]=1e9+1;
else k[i]=b[i]=-(double)A/B;
}
sort(b+1,b+n+1);
int m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i)
{
a[i]=lower_bound(b+1,b+m+1,k[i])-b;
++cnt[a[i]];
}
for(int i=1;i<=m;++i)
sum+=cnt[i]*cnt[i];
lxl ans=0;
for(int i=1;i<=n;++i)
ans+=1ll*((n-cnt[a[i]])*(n-cnt[a[i]])-(sum-cnt[a[i]]*cnt[a[i]]));
printf("%lld\n",ans/6ll);
return 0;
}
T2
用平衡树模拟进站过程,每次找到队尾元素编号大于进站编号的队列中队尾元素编号最小的队列,进入这个队列,若不存在这样的队列,则进入一个新的队列。
因为只有队尾的元素会产生影响,所以只需要保存队尾元素编号即可。支持查询前驱和单点修改,使用平衡树即可。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <ctime>
#include <climits>
using namespace std;
const int maxn=1e5+5;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
}
int n;
struct node
{
int siz,val;
node *ls,*rs;
node(int siz,int val,node *ls,node *rs):siz(siz),val(val),ls(ls),rs(rs){}
node(){}
}*root,*null,*st[maxn<<2],tt[maxn<<2];
const int ratio=4;
int cnt;
#define new_node(a,b,c,d) (&(*st[cnt++]=node(a,b,c,d)))
#define merge(a,b) new_node(a->siz+b->siz,b->val,a,b)
inline void update(node *p)
{
if(!p->ls->siz) return;
p->siz=p->ls->siz+p->rs->siz;
p->val=p->rs->val;
}
inline void rotate(node *p)
{
if(p->ls->siz > p->rs->siz*ratio)
p->rs=merge(p->ls->rs,p->rs),st[--cnt]=p->ls,p->ls=p->ls->ls;
if(p->rs->siz > p->ls->siz*ratio)
p->ls=merge(p->ls,p->rs->ls),st[--cnt]=p->rs,p->rs=p->rs->rs;
}
void insert(node *p,int d)
{
if(p->siz==1)
{
p->ls=new_node(1,min(p->val,d),null,null);
p->rs=new_node(1,max(p->val,d),null,null);
}
else insert(p->ls->val>=d ? p->ls : p->rs,d);
update(p);rotate(p);
}
void erase(node *p,int d)
{
if(p->ls->siz==1&&p->ls->val==d)
st[--cnt]=p->ls,st[--cnt]=p->rs,*p=*p->rs;
else if(p->rs->siz==1&&p->rs->val==d)
st[--cnt]=p->ls,st[--cnt]=p->rs,*p=*p->ls;
else erase(p->ls->val>=d ? p->ls : p->rs,d);
update(p);rotate(p);
}
int kth(node *p,int k)
{
if(p->siz==1) return p->val;
return p->ls->siz>=k ? kth(p->ls,k) : kth(p->rs,k-p->ls->siz);
}
int rnk(node *p,int d)
{
if(p->siz==1) return 1;
return p->ls->val>=d ? rnk(p->ls,d) : p->ls->siz+rnk(p->rs,d);
}
int nxt(int d) {return kth(root,rnk(root,d+1));}
void print(node *p)
{
if(p->siz==1) printf("%d ",p->val);
else print(p->ls),print(p->rs);
}
inline void init()
{
null=new node(0,0,0,0);
for(int i=0;i<=(maxn<<1);++i) st[i]=&tt[i];
root=new_node(1,INT_MAX,null,null);
}
int main()
{
// freopen("manage.in","r",stdin);
// freopen("manage.out","w",stdout);
read(n);
init();
for(int i=1,p;i<=n;++i)
{
read(p);
int x=nxt(p);
if(x==INT_MAX)
insert(root,p);
else
{
erase(root,x);
insert(root,p);
}
// print(root);puts("");
}
printf("%d\n",root->siz-1);
return 0;
}
T3
这题我人都傻了,题解还没有证明它的正确性……
考场上写了个随机化算法拿了10pt/kk。
题解:
- 10 分做法:任何暴力方法,例如枚举所有可能的 \(maxG\) 和 \(maxS\),然后判断连通性。
- 30 分做法: 按照 \(g\) 属性从小到大排序,枚举 \(maxG\),对满足 \(maxG\) 的所有道路按照 \(s\) 属性从小到大排序,然后做kruskal,时间复杂度 \(O(m^2\log m)\)。
- 50 分做法: 在 30 分基础上,发现每次只增加一条边,插入到上次的边集合中再做 kruskal 即可, 时间复杂度 \(O(m^2)\)。
- 100 分做法: 依旧按照 \(g\) 属性从小到大排序。不断加入新边的过程中发现,当前的最小生成树只可 能是由未加入新边的最小生成树的边和当前新边组成的共 \(n\) 条边中选出 \(n-1\) 条构成。因 此 维护一个最小生成树边集,每次只在 \(n\) 条边中做最小生成树,时间复杂度 \(O(mn)\)。
至于为什么这样做是对的,打死出题人就知道了 。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <climits>
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
const int maxn=405,maxm=5e4+5;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
}
struct edge
{
int u,v;
lxl g,s;
edge(int u,int v,lxl g,lxl s):u(u),v(v),g(g),s(s){}
edge(){}
}e[maxm];
inline bool cmp_g(const edge &a,const edge &b)
{
return a.g<b.g;
}
inline bool cmp_s(const edge &a,const edge &b)
{
return a.s<b.s;
}
int n,m;
int fa[maxn];
lxl wG,wS;
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
inline lxl Kruskal(vector<edge> &S,vector<edge> &T)
{
for(int i=1;i<=n;++i) fa[i]=i;
T.clear();
sort(S.begin(), S.end(),cmp_s);
int cnt=0;
lxl MaxG=0,MaxS=0;
for(auto e:S)
{
int u=e.u,v=e.v;
int x=find(u),y=find(v);
if(x==y) continue;
MaxG=max(MaxG,e.g);
MaxS=max(MaxS,e.s);
fa[x]=y;
T.push_back(e);
++cnt;
if(cnt==n-1) break;
}
return cnt==n-1?MaxG+MaxS:LLONG_MAX;
}
vector<edge> vec[2];
int main()
{
// freopen("road.in","r",stdin);
read(n),read(m);
read(wG),read(wS);
int u,v;lxl g,s;
for(int i=1;i<=m;++i)
{
read(u),read(v),read(g),read(s);
e[i]=edge(u,v,g*wG,s*wS);
}
sort(e+1,e+m+1,cmp_g);
lxl ans=LLONG_MAX;
for(int i=1;i<=m;++i)
{
int id=i&1;
vec[id].push_back(e[i]);
ans=min(ans,Kruskal(vec[id],vec[id^1]));
}
printf("%lld\n",ans);
return 0;
}