[32](CSP 集训) CSP-S 模拟 3
A 奇观
考虑到 CCF 可以拆开算,答案为 \((ans_c)^2\times ans_f\)
剩下的东西比较少,考虑 DP
我的 dp 是从爆搜改的,设 \(f_{i,j}\) 表示递归到第 \(i\) 层时 \(j\) 的方案数,原始的爆搜代码如下:
int dfs_f2(int now,int id){
if(now==0) return 1;
if(now==1) return f_c[0][id];
if(f_c[now][id]) return f_c[now][id];//cth<<"dfs "<<now<<" "<<id<<endl;
int res=0;
if(now!=2){
for(int i=1;i<=n;++i){
if(i!=id and hdk.find(i,id)){
res+=dfs_f2(now-1,i);
res%=p;
}
}
}
else{
for(int i=1;i<=n;++i){
if(i!=id and hdk.find(i,id)){
res+=f_c[0][i]*f_c[0][id];
res%=p;
}
}
}
return f_c[now][id]=res%p;
}
可以发现该搜索的最大问题在于内层枚举时的循环,注意到此题是完全图删边形成的,因此考虑做容斥,用总方案数减去不合法的,不合法的可以通过枚举得到
我写的 dp 比较异端,看个乐子就行
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;++i){
f_c[0][i]=n-1;
}
for(int i=1;i<=m;++i){
int x,y;scanf("%lld %lld",&x,&y);
del_e[x].push_back(y);
del_e[y].push_back(x);
f_c[0][x]--;f_c[0][y]--;
}
int sum=0;
for(int i=1;i<=n;++i){
sum=(sum+f_c[0][i])%p;
}
for(int i=1;i<=n;++i){
f_c[1][i]=fixed(sum-f_c[0][i]);
for(int j:del_e[i]) f_c[1][i]=fixed(f_c[1][i]-f_c[0][j]);
}
sum=0;
for(int i=1;i<=n;++i){
sum=(sum+f_c[1][i])%p;
}
for(int i=1;i<=n;++i){
f_c[2][i]=fixed(sum-f_c[1][i]);
for(int j:del_e[i]) f_c[2][i]=fixed(f_c[2][i]-f_c[1][j]);
}
sum=0;
for(int i=1;i<=n;++i){
sum=(sum+f_c[2][i])%p;
}
int c=sum%p;
memset(f_c[1],0,sizeof f_c[1]);
memset(f_c[2],0,sizeof f_c[2]);
memset(f_c[3],0,sizeof f_c[3]);
sum=0;
for(int i=1;i<=n;++i){
sum=(sum+f_c[0][i])%p;
}
for(int i=1;i<=n;++i){
f_c[1][i]=fixed((sum-f_c[0][i])*f_c[0][i]);
for(int j:del_e[i]){
f_c[1][i]=fixed(f_c[1][i]-f_c[0][j]*f_c[0][i]);
}
}
sum=0;
for(int i=1;i<=n;++i){
sum=(sum+f_c[1][i])%p;
}
for(int i=1;i<=n;++i){
f_c[2][i]=fixed(sum-f_c[1][i]);
for(int j:del_e[i]){
f_c[2][i]=fixed(f_c[2][i]-f_c[1][j]);
}
}
int f=0;
for(int i=1;i<=n;++i){
f=(f+f_c[2][i])%p;
}
cout<<fixed(c*c%p*f)<<endl;
B 铁路
非常好的题
注意到我们没有必要非得去给每次操作新建边,我们可以直接在树上跳 lca 来用并查集合并,这样能解决我们求节点数目的问题:每进行一次操作,节点数加一,并查集上每进行一次合并,节点数减一
然后考虑怎么优化这个过程,注意到,已经在同一个并查集里的数是不需要合并的,因此我们可以借助并查集维护相关信息来直接跳过这段并查集,那么维护什么东西能快速跳节点呢,因为我们是在向根跳,因此我们直接维护并查集中深度最小的节点编号就好了,维护的时候直接往深度最小的节点跳。有人会问这么跳,万一跳过头了怎么办,其实无所谓,因为既然你能跳过头,说明你的目标节点一定在当前并查集内,直接判断当前点和目标点是否在同一并查集内即可
随后我们需要解决新建节点的问题,因为我们并没有真的新建节点,那么如果它询问我们一个新的节点编号,我们要知道往哪里去找。这是很好办的,因为新建节点的下属节点一定已经被合并在同一个并查集里了,因此我们直接维护一个下标,存储每一个新建节点对应的并查集的深度最小的点就行了
#include<bits/stdc++.h>
using namespace std;
#define io_h
// #include"./include/hdk/lib.h"
namespace reader{
template<typename T>
inline void read(T& x){
x=0;bool sym=0;char c=getchar();
while(!isdigit(c)){sym^=(c=='-');c=getchar();}
while(isdigit(c)){x=x*10+c-48;c=getchar();}
if(sym)x=-x;
}
template<size_t N>
inline void read(char (&str)[N]){
size_t n=0;char c=getchar();
while(n<N-1&&!isspace(c)){str[n]=c;c=getchar();++n;}
str[n]=0;
}
template<typename T,typename... Args>
inline void read(T& x,Args&... args){
read(x);read(args...);
}
}
int deep[1000001];
using namespace reader;
template<int size>
struct dsu{
int fa[size+1],minn[size+1];
void clear(int n){
for(int i=1;i<=n;++i){
fa[i]=i;
minn[i]=i;
}
}
int find(int id){
if(id==fa[id]){
return id;
}
fa[id]=find(fa[id]);
return fa[id];
}
void join(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy){
fa[fx]=fy;
if(deep[fx]>deep[fy]){
minn[fx]=minn[fy];
}
}
}
};
int n,m;
int fa[1000001];
dsu<(int)1e6>d;
int ans=0;
int to[1000001];
void join(int x,int y,int newid){
// cout<<"join "<<x<<" "<<y<<endl;
int ori=x;
while(x!=y){
// cout<<"... "<<x<<" "<<y<<"("<<deep[x]<<","<<deep[y]<<")"<<endl;
if(d.find(x)!=x){
// cout<<"find x("<<x<<")!="<<d.find(x)<<endl;
x=d.minn[d.find(x)];
continue;
}
if(d.find(y)!=y){
// cout<<"find y("<<y<<")!="<<d.find(y)<<endl;
y=d.minn[d.find(y)];
continue;
}
if(deep[x]>deep[y]){
d.join(x,fa[x]);
ans--;
x=fa[x];
}
else{
d.join(y,fa[y]);
ans--;
y=fa[y];
}
}
to[newid]=ori;
}
vector<int>e[500001];
void dfs(int now,int last){
fa[now]=last;
deep[now]=deep[last]+1;
for(int i:e[now]){
if(i!=last){
dfs(i,now);
}
}
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
read(n,m);
d.clear(n+m);
for(int i=1;i<=n-1;++i){
int x,y;read(x,y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
for(int i=1;i<=m;++i){
int x,y;read(x,y);
if(x>n) x=to[x];
if(y>n) y=to[y];
join(x,y,n+i);
cout<<n+ans<<endl;
}
}
C.光纤
非常不好的题
旋转卡壳是好的,就是给你一个凸包,让你维护凸包直径,也就是一条直线到凸包内所有节点的最大距离最小
有一个结论,这样的直线一定与凸包的某一条边平行,所以你只需要绕着凸包转一圈就能搞定这个问题
问题是怎么确定凸包上哪个点到这条直线距离最大,有一个显然的 \(n^{2}\) 做法
但实际上不是的,因为这个决策点也有单调性,所以开一个双指针就做完了
这题不好,唐诗有理数类
一开始用的平面向量求点线距做的,后来调不出来了,所以改叉积了
#include<bits/stdc++.h>
using namespace std;
#define int __int128
#define abs(x) (x>=0?x:-x)
void _print(__int128 x,bool first=true){
if(x<0){
putchar('-');
_print(-x,false);
return;
}
if(x==0){
if(first) putchar('0');
return;
}
_print(x/10,false);
putchar((int)(x%10)+'0');
}
namespace reader{
template<typename T>
inline void read(T& x){
x=0;bool sym=0;char c=getchar();
while(!isdigit(c)){sym^=(c=='-');c=getchar();}
while(isdigit(c)){x=x*10+c-48;c=getchar();}
if(sym)x=-x;
}
template<size_t N>
inline void read(char (&str)[N]){
size_t n=0;char c=getchar();
while(n<N-1&&!isspace(c)){str[n]=c;c=getchar();++n;}
str[n]=0;
}
template<typename T,typename... Args>
inline void read(T& x,Args&... args){
read(x);read(args...);
}
}
using namespace reader;
class frac{
private:
int z,m;
public:
frac(int x=0,int y=1){
z=x,m=y;
fixed();
}
frac fixed(){
int gcd=__gcd(abs(z),abs(m));
if(m<0){
m*=-1;z*=-1;
}
if(z==0){
m=1;return *this;
}
if(gcd==0) return *this;
z/=gcd;m/=gcd;
return *this;
}
frac upside(){
return frac(m,z);
}
frac operator = (pair<int,int>A){
z=A.first;m=A.second;fixed();
return *this;
}
frac operator + (frac A){
return (frac(z*A.m+m*A.z,m*A.m)).fixed();
}
frac operator * (frac A){
// cout<<"multi ";this->print();putchar(' ');
// A.print();putchar('=');
int gcd1=__gcd(z,A.m);
int gcd2=__gcd(A.z,m);
frac ans=(frac((z/gcd1)*(A.z/gcd2),(m/gcd2)*(A.m/gcd1))).fixed();
// ans.print();putchar('\n');
return ans;
}
frac operator / (frac A){
return (*this*A.upside()).fixed();
}
frac operator -(){
return frac(-z,m);
}
frac operator -(frac A){
return *this+(-A);
}
bool operator <(frac A){
return z*A.m<A.z*m;
}
bool operator ==(frac A){
return z*A.m==A.z*m;
}
bool operator >(frac A){
return !(*this==A and *this<A);
}
void print(){
fixed();
_print(z);putchar('/');_print(m);
// cout<<z<<"/"<<m<<endl;
}
frac _abs(){
return frac(abs(z),abs(m));
}
long double it(){
return z*1.0/m;
}
};
int n;
int q[1000001],top;
int q1[1000001],top1;
struct node{
__int128 x,y;
}d[1000001],sta[1000001];
#define K(i,j) ((double)(d[j].y-d[i].y)/(d[j].x-d[i].x))
int cha(int x,int y,int z){
return (d[y].x-d[x].x)*(d[z].y-d[x].y)-(d[y].y-d[x].y)*(d[z].x-d[x].x);
}
int len(int x,int y){
return (d[y].x-d[x].x)*(d[y].x-d[x].x)+(d[y].y-d[x].y)*(d[y].y-d[x].y);
}
frac ans;
char anss[1000001];
inline void print(__int128 x){
if(!x)putchar('0');
else{
if(x<0)x=-x,putchar('-');
int cnt=0;
while(x)anss[++cnt]=x%10+'0',x/=10;
for(int i=cnt;i;--i)
putchar(anss[i]);
}
}
signed main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
read(n);
if(n<=2){
ans.print();
return 0;
}
for(int i=1;i<=n;++i){
read(d[i].x,d[i].y);
}
sort(d+1,d+1+n,[](const node&x,const node&y){return x.x==y.x?x.y<y.y:x.x<y.x;});
n=unique(d+1,d+1+n,[](const node&x,const node&y){return x.x==y.x&&x.y==y.y;})-d-1;
for(int i=1;i<=n;++i){
while(top>1 and (K(q[top],i)<=K(q[top-1],q[top]))){
--top;
}
q[++top]=i;
}
for(int i=n;i;--i){
while(top1>1 and K(i,q1[top1])<=K(q1[top1],q1[top1-1]))--top1;
q1[++top1]=i;
}
for(int i=2;i<top1;++i){
q[++top]=q1[i];
}
for(int i=1;i<=top;++i){
sta[i]=d[q[i]];
}
for(int i=1;i<=top;++i){
d[i]=sta[i];
}
int now=1;
for(int i=1;i<=top;++i){
while((now==(i==top?1:i+1))||((abs(cha(now,i,(i==top?1:i+1))))<=(abs(cha((now==top?1:now+1),i,(i==top?1:i+1)))))){
now=(now==top?1:now+1);
}
if(ans==frac(0,1) or ans.it()>(double)cha(now,i,(i==top?1:i+1))*cha(now,i,(i==top?1:i+1))/len(i,(i==top?1:i+1))){
ans=frac(cha(now,i,(i==top?1:i+1))*cha(now,i,(i==top?1:i+1)),len(i,(i==top?1:i+1)));
}
}
(ans*frac(1,4)).print();
}