E
格点思想
空间中多个互不重合的长方体,端点都在[0,100][0,100][0,100],两个长方体有一个面公共面积不为0即为接触,问每个长方体和多少个长方体接触?
n=2e5n=2e5n=2e5个长方体,直接做会爆炸。但是这种整点上,并且值域不大的计算几何,可以考虑直接枚举每个格点。(其实格点思想就类似于一般问题里的考虑转成值域上思考)
这里就是我们注意到在这个范围内,只有3∗10033*100^33∗1003个1∗11*11∗1的格点,并且由于长方体互不重合,每个格点最多又两个长方体,即两侧各一个。故我们对于每个格点,记录上面有哪些长方体,然后枚举每个格点,把两侧的长方体都放进对方的接触集合内(对每个长方体用一个set记录和它接触的长方体编号),对于两个长方体在不止一个格点接触的情况,set可以去重,不用管。最后set的大小就是答案
这需要先把每个长方体的所有面上的格点中,都存上当前长方体的编号,这乍看复杂度很高,但是前面已经分析得到了,每个格点最多两个长方体,故这部分复杂度不超过格点个数*2,即6∗10036*100^36∗1003,并不大
有三个维度,可以写个函数来复用,每次交换数据维度即可,即最开始考虑所有z=Cz=Cz=C的平面上的格点,然后swap(xi,zi)swap(x_i,z_i)swap(xi,zi),然后考虑所有x=Cx=Cx=C的平面上的格点,以此类推…
vi id[110][110][110];
void solve(void){
int n;
cin>>n;
vi x1(n+1),y1(n+1),z1(n+1),x2(n+1),y2(n+1),z2(n+1);
rep(i,1,n){
cin>>x1[i]>>y1[i]>>z1[i]>>x2[i]>>y2[i]>>z2[i];
}
vi ans(n+1);
int cnt=0;
auto work=[&]()->void{
cnt++;
rep(i,0,100){
rep(j,0,100){
rep(k,0,100){
id[i][j][k].clear();
}
}
}
vector<vector<array<int,5>>>p(110);
rep(i,1,n){
rep(j,x1[i],x2[i]-1)
rep(k,y1[i],y2[i]-1)
id[z1[i]][j][k].push_back(i),
id[z2[i]][j][k].push_back(i);
}
rep(i,0,100){
set<pii>vis;
rep(j,0,100){
rep(k,0,100){
if(id[i][j][k].size()>1){
int x=id[i][j][k][0],y=id[i][j][k][1];
if(!vis.count({x,y})&&!vis.count({y,x})){
ans[x]++;
ans[y]++;
vis.insert({x,y});
}
}
}
}
}
};
work();
rep(i,1,n){
swap(x1[i],z1[i]);
swap(x2[i],z2[i]);
}
work();
rep(i,1,n){
swap(x1[i],z1[i]);
swap(x2[i],z2[i]);
}
rep(i,1,n){
swap(y1[i],z1[i]);
swap(y2[i],z2[i]);
}
work();
rep(i,1,n){
cout<<ans[i]<<'\n';
}
}
F
贪心 二分
瓶子不需要开罐器就能获得价值,罐头需要用一次开罐器才能获得价值,一个开罐器最多开xix_ixi次罐头
给一堆瓶子,罐头,开罐器,最多选m个,问最大价值和?
贪心的想肯定先选价值大的,或者使用次数多的开罐器,故对这三种东西分别按权值降序排序。
接下来有多个维度的问题肯定考虑枚举。
然后瓶子是没有其他限制的,先不考虑他。罐头个数确定,我们可以二分出来至少用几个开罐器,也就是开罐器是确定的,剩下的可以都选瓶子。而枚举开罐器个数,我们只能确定最多多少个罐头,但是不一定真的要开这么多,因为可能后面的罐头价值不如瓶子了,有点麻烦
所以还是枚举罐头个数,为了二分快速check,以及快速计算剩余名额全给瓶子最大价值,对瓶子和开罐器都做前缀和。
剩下是一些细节,需要考虑:没有开罐器,开罐器不够开当前罐头,剩余名额给瓶子用不完,罐头+开罐器个数超过m
void solve(void){
int n,m;
cin>>n>>m;
vi a,b,c;
rep(i,1,n){
int t,x;
cin>>t>>x;
if(t==0)a.push_back(x);
else if(t==1)b.push_back(x);
else c.push_back(x);
}
sort(a.begin(),a.end(),greater<int>());
sort(b.begin(),b.end(),greater<int>());
sort(c.begin(),c.end(),greater<int>());
int sza=a.size();
rep(i,1,sza-1){
a[i]+=a[i-1];
}
int ans=0;
if(sza)ans=a[min(sza,m)-1];
int szc=c.size();
if(!szc){
if(!sza)cout<<0;
else cout<<a[min(m,sza)-1];
return;
}
rep(i,1,szc-1){
c[i]+=c[i-1];
}
int szb=b.size();
rep(i,1,szb-1){
b[i]+=b[i-1];
}
rep(i,0,szb-1){
int l=1,r=szc;
while(l<=r){
int m=l+r>>1;
if(c[m-1]>=i+1)r=m-1;
else l=m+1;
}
if(l>szc)break;
int cost=l+i+1;
if(cost>m)continue;
if(!sza||m==cost){
ans=max(ans,b[i]);
}
else{
ans=max(ans,b[i]+a[min(sza,m-cost)-1]);
}
}
cout<<ans;
}
G
树上计数
问有多少个三个点的组合,满足不在一条简单路径上?
两种思路,
直接统计:三个点的关系一定是,在一个点为根的树上的三个不同子树内,想从一个点到另一个点必须经过这个根,也就是对于三个点来说这个根是唯一的
所以我们枚举根,然后统计每个点为根下,不同子树的三个点的组合即可,这样是不重不漏的
正难则反:
正着不好想可以反着,三个点组合总数是Cn3C_n^3Cn3,在一条简单路径上的点,一定是三个点连成一串,可以看成是中间那个点为根,另外两个点在不同子树下,这就是经典的选两个点在不同子树中的方案数,∑szv∗(tot−szv)\sum sz_v*(tot-sz_v)∑szv∗(tot−szv)即可,注意这里往父亲方向走也是一个子树,(因为我们枚举的是每个点作为根的情况),所以tottottot不是szu−1sz_u-1szu−1,而是n−1n-1n−1
并且这样统计每个点对会被统计两遍,需要除二
void solve(void){
int n;
cin>>n;
vvi g(n+1);
rep(i,1,n-1){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
vi sz(n+1,1);
auto &&dfs1=[&](auto &&dfs1,int u,int f)->void{
for(int v:g[u]){
if(v==f)continue;
dfs1(dfs1,v,u);
sz[u]+=sz[v];
}
};
int ans=n*(n-1)*(n-2)/6;
int sum=0;
auto &&dfs2=[&](auto &&dfs2,int u,int f)->void{
for(int v:g[u]){
if(v==f){
sum+=(n-sz[u])*(n-1-(n-sz[u]));
}
else{
sum+=sz[v]*(n-1-sz[v]);
dfs2(dfs2,v,u);
}
}
};
dfs1(dfs1,1,0);
dfs2(dfs2,1,0);
cout<<ans-sum/2;
}