CQOI2016(8.17~8.18模拟赛)

一P4124 [CQOI2016]手机号码

题意:

手机号码一定是 11 位数,前不含前导的 0。工具接收两个数 L 和 R,自动统计出 [L,R] 区间内所有满足条件的号码数量。L 和 R 也是 11 位的手机号码。
条件:号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。

做法:

数位 \(dp\)。

先将问题拆分成 \(R\) 之前的合理号码减去 \(L-1\) 之前的合理号码。

深搜,记录深搜位数,前两个数字,是否有 3 个连续的数字,是否有 8 和 4,是否卡上界,加上记忆化即可。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int t[15];
ll r,l,f[12][12][12][2][2][2][2];
ll dfs(int p,int a,int b,bool t3,bool k,bool t4,bool t8)
{
	if(t4&&t8) return 0;
	if(p==0) return t3;
	if(f[p][a][b][t3][k][t4][t8]!=-1) return f[p][a][b][t3][k][t4][t8];
	ll num=0;
	int up=k?t[p]:9;
	for(int i=0;i<=up;i++)
	num+=dfs(p-1,b,i,t3||(a==b&&b==i),k&&(i==up),t4||(i==4),t8||(i==8));
	return f[p][a][b][t3][k][t4][t8]=num;
}
ll work(ll n)
{
	if(n<1e10) return 0;
	memset(f,-1,sizeof(f));
	int p=0;
	while(n){
		t[++p]=n%10;
		n/=10;
	}
	ll ans=0;
	for(int i=1;i<=t[11];i++)
	ans+=dfs(10,-1,i,0,i==t[11],i==4,i==8);
	return ans;
}
int main()
{
	cin>>l>>r;
	cout<<work(r)-work(l-1);
 } 

二 P4123 [CQOI2016]不同的最小割

题意:

给定一张无向图的流网络,求选任意(每)两个数作为源点和汇点,共有多少种数值不同的最小割。

最小割树:

最小割树的定义如下:定义一棵树T为最小割树,如果对于树上的所有边(s,t),树上去掉(s,t)后产生的两个集合恰好是原图上(s,t)的最小割把原图分成的两个集合,且边(u,v)的权值等于原图上(u,v)的最小割. 原图上u,v两点最小割就是最小割树上u到v的路径上权值最小的边。

证明:

设 \(x\),\(y\) 之间有连边,\(y\),\(z\)之间有连边,先证明 \(x\),\(z\) 作为源点和汇点的最小割一定是两边中最小的。

如果比两边最小值更小,则显然 \(x\),\(y\) 或 \(y\),\(z\) 的连边有一方可以更小,因为,\(x\),\(z\) 的最小割将图分为两部分,那么 \(y\) 点要么位于 \(x\) 的集合中,要么位于 \(z\) 的集合中,如果这种更优的割将 \(y\) 分到 \(z\) 中,那么 \(x\),\(y\) 的连边可以更小,反之 \(y\),\(z\) 间的连边可以更小。

如果比任意一边的值更大,那么用 \(x\),\(y\) 这个最小割,根据定义 \(z\) 被分到 \(y\) 那边,则这种割离方法也可将 \(x\),\(y\) 分开,因此 \(x\),\(y\) 的这种割就是一个更优的解,\(y\),\(z\) 的连边同理。

之后递推到多点即可得到上述结论。

构建:

先随机选两个点计算最小割,然后按最后一次 \(bfs\) 的深度排序,深度为零的即为在汇点一方,否则为源点一方,从排序后的数列中找出分界点,分治即可。如果一个集中只有一个点,即 \(l==r\) ,返回。

关键代码:

void solve(int l,int r)
{
	if(l==r) return;
	s=a[l],t=a[r];
	for(int p=2;p<=too;p++) val[p]=vval[p];
	int num=0;
	while(bfs()) num+=dfs(s,inf);
	adt(s,t,num);
	adt(t,s,num);
	sort(a+l,a+r+1,cmp);
	int pp; 
	for(int i=l;i<=r;i++) 
	if(dep[a[i]]) {
		pp=i;break;
	}
	solve(l,pp-1);solve(pp,r);
}

配合倍增即可 \(logn\) 求出任意两点作为最源点汇点的最小割。例题:【模板】最小割树(Gomory-Hu Tree)

做法:

算出最小割树的每一个树边即可。

code:

#include<bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1e3+8,M=4e4;
int fr[N],to[M],nxt[M],val[M],too=1,cur[N];
int n,m,s,t;
int dep[N];
int vval[M];
int a[N];
queue<int> q;
void add(int x,int y,int z)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
	vval[too]=z;
	to[++too]=x;
	nxt[too]=fr[y];
	fr[y]=too;
	vval[too]=0;
}
bool bfs()
{
	for(int i=1;i<=n;i++)
	dep[i]=0,cur[i]=fr[i];
	dep[s]=1;
	q.push(s);
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=fr[x];i;i=nxt[i])
		{
			int y=to[i];
			if(dep[y]==0&&val[i])
			{
				q.push(y);
				dep[y]=dep[x]+1;
			}
		}
	}
	return dep[t];
}
int dfs(int x,int in)
{
	if(x==t) return in;
	int num=0;
	for(int i=cur[x];i&&in;i=nxt[i])
	{
		int y=to[i];
		if(dep[y]==dep[x]+1&&val[i])
		{
			int res=dfs(y,min(in,val[i]));
			in-=res;num+=res;
			val[i]-=res;val[i^1]+=res;
		}
		cur[x]=i;
	}
	if(num==0) dep[x]=0;
	return num;
}
set <int> qp;
bool cmp(int a,int b){
	return dep[a]<dep[b];
}
void solve(int l,int r) //关键函数
{
	if(l==r) return ;
	int num=0;
	for(int p=2;p<=too;p++) val[p]=vval[p];
	s=a[l];t=a[r];
	while(bfs())
	num+=dfs(s,inf);
	qp.insert(num);
	sort(a+l,a+r+1,cmp);
	int pp=0;
	for(int i=l;i<=r;i++)
	{
		if(dep[a[i]]) {
			pp=i;break;
		}
	}
	solve(l,pp-1);solve(pp,r);
}
signed main()
{
	cin>>n>>m;
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	for(int i=1;i<=n;i++) a[i]=i;
 	solve(1,n);
    cout<<qp.size();
}

三 P4357 [CQOI2016]K 远点对

题意:

给平面内 N 个点的坐标,求欧氏距离下的第 K 远点对距离的平方。

KD tree:

根据横纵坐标将点逐渐分离,构建成一个类似于二叉排序树的形式。每个点还要记录分离其时自己所代表的矩形四点坐标。(即此节点及其子树中横纵坐标的最大最小值)。操作时根据极值判断是否有遍历此子树的必要性。

关键代码:可以本题为参考。

做法:

建立 \(KD tree\) ,先将 \(K*=2\) 不考虑后面两点重复的问题。

建立一个小根堆,先扔进去 \(K(2K)\) 个 \(0\),后面找所有能比堆顶大的,找到一个将其加入堆中,将堆顶踢出。这样操作下来所有堆中元素都是大于等于堆顶的,且刚好有 \(K(2K)\) 个大于堆顶的,输出堆顶即可。

对于找大于堆顶的点对,枚举每个点,再将其在 \(KD tree\) 上跑即可。

code:

#include<bits/stdc++.h>
#define ls tr[k].l
#define rs tr[k].r
#define ll long long
using namespace std;
const int N=1e5+6;
priority_queue <ll> q;
struct tree{
	int l,r;
	ll maxx,maxy,minx,miny,x,y;
} tr[N];
struct node {
	ll x,y;
} a[N];
int n,K,flag,tot=0,rt;
bool cmp(node x,node y){
	if(flag) return x.y<y.y;
	return x.x<y.x;
}
void push(int k)
{
    if(ls){
        tr[k].maxx=max(tr[k].maxx,tr[ls].maxx);
        tr[k].maxy=max(tr[k].maxy,tr[ls].maxy);
        tr[k].minx=min(tr[k].minx,tr[ls].minx);
        tr[k].miny=min(tr[k].miny,tr[ls].miny);
    }
    if(rs){
        tr[k].maxx=max(tr[k].maxx,tr[rs].maxx);
        tr[k].maxy=max(tr[k].maxy,tr[rs].maxy);
        tr[k].minx=min(tr[k].minx,tr[rs].minx);
        tr[k].miny=min(tr[k].miny,tr[rs].miny);
    }
}
void build(int &k,int l,int r,int op)
{
	if(l>r) return;
	flag=op;
	int mid=(l+r)>>1;
	nth_element(a+l,a+mid,a+r+1,cmp);
	k=++tot;
	tr[k].maxx=tr[k].minx=tr[k].x=a[mid].x;
	tr[k].maxy=tr[k].miny=tr[k].y=a[mid].y;
	build(tr[k].l,l,mid-1,op^1);
	build(tr[k].r,mid+1,r,op^1);
	push(k);
}
ll dis(int x,int y,int p){
	return (x-a[p].x)*(x-a[p].x)+(y-a[p].y)*(y-a[p].y);
}
ll get(int k,int p)
{
	if(!k) return -1e18;
    ll dis1=dis(tr[k].maxx,tr[k].maxy,p),dis2=dis(tr[k].maxx,tr[k].miny,p),dis3=dis(tr[k].minx,tr[k].maxy,p),dis4=dis(tr[k].minx,tr[k].miny,p);
    return max(max(dis1,dis2),max(dis3,dis4));
}
void solve(int k,int p)
{
	ll dist=dis(tr[k].x,tr[k].y,p);
	if(dist>-q.top()) q.pop(),q.push(-dist);
    ll dl=get(tr[k].l,p),dr=get(tr[k].r,p);
    if(dl>-q.top())  solve(tr[k].l,p);
    if(dr>-q.top())  solve(tr[k].r,p);
}
int main()
{
	cin>>n>>K;
	K<<=1;
	for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
	for(int i=1;i<=K;i++) q.push(0);
	build(rt,1,n,0);
	for(int i=1;i<=n;i++) solve(rt,i);
	cout<<-q.top();
}

四: P5768 [CQOI2016]路由表

题意:

两种操作:

\(A\) \(S\) \(len\) 加入一个长度为 \(len\) 的新字符串。\(len<=32\),任意两个串不同。

\(Q\) \(S\) \(l\) \(r\) 求先从 \(1\) 到 \(l-1\) 的字符串中找到匹配最长的,再从 \(l\) 到 \(r\) 看最长匹配的串会更新几次。

做法:

建立 \(tire\) 树,记录 \(ed[i]\) 为 \(i\) 处结尾的串的编号。

每次插入直接插,询问的话建立一个大根堆,遍历询问的串,如果找到一个编号小于 \(l\) 的,则清空堆,如果大于 \(r\) 直接忽略,位于 \(l\),\(r\) 之间则删掉所有比此序号大的点,保证每一次堆内都是在此时间之前的点。最后直接输出堆大小即可。(代码做法)

也可以先遍历完,将出现的序号放进数组中,倒序遍历,记录一个 \(last\) 为上一个序号位于 \(l\),\(r\) 之间的最小的序号,对于大于 \(r\) 的跳过,位于 \(l\),\(r\) 之间则判断是否小于 \(last\),小于则 \(ans++\) 并更新 \(last\),大于则继续,如果序号小于 \(l\) 则终止。这样可去掉堆的 \(log\)。(后交的lmy的代码)

code:

#include <bits/stdc++.h>
using namespace std;
const int mn=1e6+7,inf=1e9;
struct tree {
    int ch[2];
    vector<int> a;
}tr[mn*10];
char s[100];
int tt,tot=0,bit[100],L,R,q[100],tt2;
void chg(int x)
{
    for(int i=7;i>=0;--i,++tt)
      bit[tt]=((x&(1<<i))==0?0:1);
}
void ins(int id)
{
    int now=0;
    for(int i=1;i<=tt;++i)
    {
        if(!tr[now].ch[bit[i]])  tr[now].ch[bit[i]]=++tot;
        now=tr[now].ch[bit[i]];
    }
    tr[now].a.push_back(id);
}
void get()
{
    int now=0;
    for(int i=1;i<=32;++i)
    {
        now=tr[now].ch[bit[i]];
        if(now==0)  return ;
        q[++tt2]=now;
    }
}
int find(int x)
{
    if(tr[x].a.size()==0)  return 0;
    int l=0,r=tr[x].a.size()-1,ans=inf;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(tr[x].a[mid]<L)  l=mid+1;
        else  r=mid-1,ans=min(ans,tr[x].a[mid]);
    }
    return ans;
}
int sol()
{
    int ans=0,pos=R+1;
    for(int i=tt2;i>0;--i)
    {
        if(tr[q[i]].a.size()==0)  continue;
        int x=tr[q[i]].a[0];
        if(x>=L&&x<=R&&x<pos)  pos=x,++ans;
        else if(x<L) break;
    }
    return ans;
}
int main()
{
    int n,id=0;
    cin>>n;
    char ch;
    while(n--)
    {
        ch=getchar();
        while(ch!='A'&&ch!='Q')  ch=getchar();
        if(ch=='A')
        {
            ++id;
            tt=1;
            scanf("%s",s+1);
            int len=strlen(s+1),cnt=0;
            for(int i=1;i<=len;++i)
            {
                if(s[i]=='.'||s[i]=='/')  chg(cnt),cnt=0;
                else  cnt*=10,cnt+=s[i]-'0'; 
            }
            tt=cnt;
            ins(id);
        }
        else
        {
            tt=1;tt2=0;
            scanf("%s",s+1);
            int len=strlen(s+1),cnt=0;
            s[++len]='.';
            for(int i=1;i<=len;++i)
            {
                if(s[i]=='.'||s[i]=='/')  chg(cnt),cnt=0;
                else  cnt*=10,cnt+=s[i]-'0'; 
            }
            scanf("%d%d",&L,&R);
            get();
            printf("%d\n",sol());
        }
    }
    return 0;
}

五: P4359 [CQOI2016]伪光滑数

题意:略。

做法:

先把所有小于 128 的质数找出来。

考虑当一个数质因子个数一定时,则全为最大的质因子时这个伪光滑数是最大的。

建立大根堆。

先把每种质因子在小于 n 前提下把每一次方加入堆中,每一次取出一个,将其一个最大质因子换为每一个比其小的
质因子加入堆中,如果已经换过了,则只能换越来越小的。这样不重不漏考虑了所有情况。取 k 个即可。

记录数值,最大质因子个数,换数的上界,以及最大质因子是谁即可。

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int p[129],tot=0;
int b[129];
int n,K;
int f=0;
struct node{
	int val,maxp,k,nxt;
};
bool operator < (node a,node b)
{
	return a.val<b.val;
}
priority_queue <node> q;
signed main()
{
	for(int i=2;i<=128;i++)
	{
		if(b[i]) continue;
		p[++tot]=i;
		for(int j=2;i*j<=128;j++)
		b[i*j]=1;
	}
	cin>>n>>K;
	for(int i=1;i<=tot;i++)
	{
		int pp=p[i],k=1;
		while(pp<=n){
			q.push((node{pp,p[i],k,i-1}));
			k++;pp*=p[i];
		}
	}
	while(K--)
	{
		node x=q.top();
		q.pop();
		if(!K) {
			cout<<x.val;return 0;
		}
        if(x.k>1)
        for(int i=1;i<=x.nxt;i++)
        q.push((node){x.val/x.maxp*p[i],x.maxp,x.k-1,i});
	}
}
posted @ 2021-08-19 22:20  ☄️ezuyz☄️  阅读(88)  评论(0)    收藏  举报