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☄️  阅读(67)  评论(0)    收藏  举报