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&∈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});
}
}