CSP模拟10
现在有这样一种感觉:是在留下永远不会在有人看的遗产。
T1
正解并查集,直接把每次给你的 \(x,y\) 用并查集合并一下(没有 \(y\) 就把 \(x\) 和 \(0\) 合并一下)并加入答案,如果已经在一个集合里就不要加入答案就行了。下面这个不是我写的。
int main()
{
H=read(); L=read();
for(int i=0;i<=L;i++) fa[i]=i;
for(int i=1;i<=H;i++)
{
int opt=read();
if(opt==1)
{
int x=read();
if(find(x)==find(0)) continue;
ans[++pol]=i; merge(x,0);
}
else
{
int x=read(),y=read();
if(find(x)==find(y)) continue;
ans[++pol]=i; merge(x,y);
}
}
printf("%d %d\n",Qun(2,pol),pol);
for(int i=1;i<=pol;i++) printf("%d ",ans[i]);
return 0;
}
考场随手拿 \(\text{priority_queue}\) 手动模拟了一下过了。joke3579好像也是暴力模拟过的。不过他用的vector。
跑的很慢。
订正:重评以后被卡死了。
/*线性基裸题 考虑怎么插入这么大的一个数*/
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int mod=1000000007;
int n,m,a[500010];
vector<int>p[500010];
bool v[500010];
priority_queue<int>q;
void ins(int id){
while(!q.empty()){
int x=q.top(),cnt=0;
while(!q.empty()&&q.top()==x){
q.pop();cnt++;
}
if(cnt&1){
if(p[x].size()==0){
p[x].push_back(x);
while(!q.empty()){
int y=q.top(),ret=0;
while(!q.empty()&&q.top()==y){
q.pop();ret++;
}
if(ret&1)p[x].push_back(y);
}
v[id]=true;
}
else{
for(int i=0;i<p[x].size();i++){
if(p[x][i]!=x)q.push(p[x][i]);
}
}
}
}
}
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=1ll*a*ans%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int od,x,y;scanf("%d",&od);
if(od==1){
scanf("%d",&x);q.push(x);
}
else{
scanf("%d%d",&x,&y);q.push(x);q.push(y);
}
ins(i);
}
int ans=0;
for(int i=1;i<=m;i++)if(p[i].size())ans++;
printf("%d %d\n",qpow(2,ans),ans);
for(int i=1;i<=n;i++)if(v[i])printf("%d ",i);
}
T2
考场暴力写挂了光荣保龄。
我们随便找一个不是叶子的节点做根,然后考虑哪些情况不合法。
- 子树内部的节点个数比当前节点还少,那么子树消完了当前节点也消不完,不合法。
- 当前节点的2倍比子树还少,那么由于消的时候只有子树之间消(子树-2,当前节点-1)或和外面的消(子树-1,当前节点-1),那么当前节点消完了子树也消不完,不合法。
- 子树中最大的一个比当前节点还大,显然消不完,不合法。
- 消完之后根节点不为 \(0\) ,不合法。
对每个节点判一下就行了。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct node{
int v,next;
}edge[200010];
int n,t,rt,a[100010],head[100010],d[100010],val[100010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
void dfs1(int x,int f){
if(d[x]==1){
val[x]=a[x];return;
}
val[x]=a[x]<<1;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs1(edge[i].v,x);
val[x]-=val[edge[i].v];
}
}
}
void dfs2(int x,int f){
if(d[x]==1)return;
if(val[x]>a[x]){
printf("NO");exit(0);
}
int ans=0;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
if(val[edge[i].v]>a[x]-val[x])ans+=val[edge[i].v]+val[x]-a[x];
}
}
if(ans>val[x]){
printf("NO");exit(0);
}
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f)dfs2(edge[i].v,x);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
d[u]++;d[v]++;
}
if(n==2){printf("%s",a[1]==a[2]?"YES":"NO");return 0;}
for(int i=1;i<=n;i++)if(d[i]!=1){
rt=i;break;
}
dfs1(rt,0);
if(val[rt]){
printf("NO");return 0;
}
dfs2(rt,0);
printf("YES");
return 0;
}
T3
提供一种不太一样但是个人觉得比较好理解的思路。(以下省略向上取整)
我们排序之后从大到小考虑。由于我们最后的答案肯定是一个区间一个区间的,所以我们只需要知道每个区间的上下界。
考虑现在我们扫到一个点的上界是 \(r\) ,那么我们如果扫到下一个点 \(x\) 并试图扩展上界,那么扩展到的上界应当是 \(r+x\) ,因为 \(x\le r\) ,那么 \(\frac {r+x}2\le r\) ,可以扩展到。也就是说,我们每个点的上界就是这个点及后面的所有点的和,直接从小到大排序然后跑前缀和就可以。
仍然从大到小考虑,现在我们的下界是 \(l\) 。我们扫到一个点 \(x\) ,由于它比之前的都要小,所以新的下界是 \(\frac x2\) 。但是这个点的上界是 \(x\) ,而原来的下界可能扩展不到新的上界,于是就出现了一个新的连续段。
扫一遍统计所有连续段即可。代码总共20行。
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
int n,ans,a[100010],down[100010],sum[100010],pos[100010];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
sort(a+1,a+n+1);sum[0]=-1;
for(int i=1;i<=n;i++){
sum[i]=(i==1)?a[1]:(sum[i-1]+a[i]);
down[i]=(a[i]+1)/2;pos[i]=i;
if(down[i]<=sum[i-1]+1)down[i]=down[i-1],pos[i]=pos[i-1];
}
for(int i=n;i>=1;i--){
ans+=sum[i]-down[i]+1;
i=pos[i];
}
printf("%lld",ans);
}
T4
原题。不会回滚莫队。
快踩