GVKun编程网logo

【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)(绝地反击网游)

12

本文将分享【BZOJ5316】[JSOI2018]绝地反击的详细内容,并且还将对网络流,计算几何,二分进行详尽解释,此外,我们还将为大家带来关于BZOJ5301:[Cqoi2018]异或序列、bzoj

本文将分享【BZOJ5316】[JSOI2018]绝地反击的详细内容,并且还将对网络流,计算几何,二分进行详尽解释,此外,我们还将为大家带来关于BZOJ5301: [Cqoi2018]异或序列、bzoj5301[CQOI2018]异或序列、BZOJ5314: [Jsoi2018]潜入行动 (树形DP)、BZOJ5316 : [Jsoi2018]绝地反击的相关知识,希望对你有所帮助。

本文目录一览:

【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)(绝地反击网游)

【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)(绝地反击网游)

【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)

题面

BZOJ 洛谷

题解

很明显需要二分一个答案。 那么每个点可以确定的范围就是以当前点为圆心,二分出来的答案为半径画一个圆,和目标的圆的交就是可行的区间。 首先我们不知道正$n$边形的转角,如果我们知道的话,可以直接暴力网络流来进行$check$。 首先一个答案可行,意味着某个点在目标圆上覆盖的弧的两端中,一定有一个是可行的。 所以我们需要验证的转角只有$2n$个。这样子暴力跑网络流的次数是$2nlogn$次。 考虑如何优化这个过程。 发现合法的转角一共$2n$个,把它们按照转角排序,考虑从小往大改变转角的过程,显然每次会删掉一个可行的匹配,或者是加入一组可行的匹配。 那么这里删边直接退流就好了,不需要每次构图重建。 因为是二分图,退流直接手动删边就行了。 加边直接加完之后重新增广一次就行了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define MAX 605
const double Pi=acos(-1),eps=1e-8;
const int inf=1000000000;
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<''0''||ch>''9'')&&ch!=''-'')ch=getchar();
	if(ch==''-'')t=true,ch=getchar();
	while(ch<=''9''&&ch>=''0'')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,R,S,T;
struct Line{int v,next,w;}e[MAX*MAX];
int h[MAX],cnt=2,cur[MAX];
inline void Add(int u,int v,int w)
{
	e[cnt]=(Line){v,h[u],w};h[u]=cnt++;
	e[cnt]=(Line){u,h[v],0};h[v]=cnt++;
}
int level[MAX];
bool bfs()
{
	for(int i=S;i<=T;++i)level[i]=0;
	for(int i=S;i<=T;++i)cur[i]=h[i];
	queue<int> Q;level[S]=1;Q.push(S);
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		for(int i=h[u];i;i=e[i].next)
			if(e[i].w&&!level[e[i].v])
				level[e[i].v]=level[u]+1,Q.push(e[i].v);
	}
	return level[T];
}
int dfs(int u,int flow)
{
	if(u==T||!flow)return flow;
	int ret=0;
	for(int &i=cur[u];i;i=e[i].next)
	{
		int v=e[i].v,d=0;
		if(e[i].w&&level[v]==level[u]+1)
		{
			d=dfs(v,min(flow,e[i].w));
			ret+=d;flow-=d;
			e[i].w-=d;e[i^1].w+=d;
			if(!flow)break;
		}
	}
	if(!flow)level[u]=0;
	return ret;
}
void Delete(int u,int v,int &flow)
{
	int val=0;
	for(int i=h[v],j=0;i;j=i,i=e[i].next)
		if(e[i].v==u){j?(e[j].next=e[i].next):(h[v]=e[i].next);break;}
	for(int i=h[u],j=0;i;j=i,i=e[i].next)
		if(e[i].v==v){j?(e[j].next=e[i].next):(h[u]=e[i].next);val=e[i].w^1;break;}
	if(!val)return;--flow;
	for(int i=h[S];i;i=e[i].next)if(e[i].v==u){e[i].w^=1,e[i^1].w^=1;break;}
	for(int i=h[T];i;i=e[i].next)if(e[i].v==v){e[i].w^=1,e[i^1].w^=1;break;}
	if(bfs())flow+=dfs(S,inf);
}
struct Point{double x,y;}p[MAX];
struct Degree{double t;int u,v,opt;}q[MAX];
bool operator<(Degree a,Degree b){return a.t!=b.t?a.t<b.t:a.opt>b.opt;}
double alpha;
bool check(double mid)
{
	for(int i=S;i<=T;++i)h[i]=0;cnt=2;
	int tot=0;
	for(int i=1;i<=n;++i)
	{
		double dis=sqrt(p[i].x*p[i].x+p[i].y*p[i].y);
		if(dis>R+mid||dis<R-mid)return false;
		if(dis+R<=mid)
			for(int j=1;j<=n;++j)Add(i,j+n,1);
		else
		{
			double ang=atan2(p[i].y,p[i].x);
			double d=acos((1.0*R*R+dis*dis-mid*mid)/(2*R*dis));
			double l=ang-d,r=ang+d;
			while(l<0)l+=2*Pi;while(r<0)r+=2*Pi;
			int L=l/alpha,R=r/alpha;
			q[++tot]=(Degree){l-L*alpha,i,L+1,1};
			q[++tot]=(Degree){r-R*alpha,i,R+1,-1};
			++L,++R;
			if(l<=r)
				for(int j=L+1;j<=R;++j)Add(i,j+n,1);
			else
			{
				for(int j=L+1;j<=n;++j)Add(i,j+n,1);
				for(int j=1;j<=R;++j)Add(i,j+n,1);
			}
		}
	}
	for(int i=1;i<=n;++i)Add(S,i,1),Add(i+n,T,1);
	sort(&q[1],&q[tot+1]);int ans=0;
	while(bfs())ans+=dfs(S,inf);
	if(ans==n)return true;
	for(int i=1;i<=tot;++i)
		if(q[i].opt==1)
		{
			Add(q[i].u,q[i].v+n,1);
			if(bfs())ans+=dfs(S,inf);
			if(ans==n)return true;
		}
		else Delete(q[i].u,q[i].v+n,ans);
	return false;
}
int main()
{
	n=read();R=read();alpha=2*Pi/n;
	S=0;T=n+n+1;
	for(int i=1;i<=n;++i)p[i].x=read(),p[i].y=read();
	double l=0,r=400;
	while(r-l>eps)
	{
		double mid=(l+r)/2;
		if(check(mid))r=mid;
		else l=mid;
	}
	printf("%.8lf\n",l);
	return 0;
}

BZOJ5301: [Cqoi2018]异或序列

BZOJ5301: [Cqoi2018]异或序列

【传送门:BZOJ5301】


简要题意:

  给出长度为n的序列,给出m个询问,并给出k,每个询问输入l,r

  每个询问输出l到r的序列中的所有子串中的异或和为k的子串数量


题解:

  莫队

  异或,真是个神东西

  首先异或和满足前缀,也就是说设sum[i]为a[1]^a[2]^...^a[i],那么a[i]^a[i+1]^...^a[j]=sum[j]^sum[i-1]

  而且异或不仅满足交换律,而且对于a^b=c时,a^c=b,b^c=a这两个式子同样成立

  那么就好做了,假设当前i到j这个子串的异或和为k,就说明sum[j]^sum[i-1]=k,也就是sum[i-1]^k=sum[j],sum[j]^k=sum[i-1]

  然后在区间转移的时候,设cnt[i]为当前区间值为i的前缀有多少个,然后对于增加序列长度的操作,假设新加的位置为r+1,我们先将cnt[sum[r+1]]++,然后求出ans+=cnt[sum[r+1]^k],左边扩展也是如此,不过注意,向左扩展时,对ans的更新是用sum[l-1]的,因为是sum[j]与sum[i-1]可以满足前缀

  而且向右扩展的时候,如果sum[r+1]^k=sum[l-1]的话,ans++,因为我们更新的时候没有计算[l...r+1]区间的影响,所以要维护一下

  而对于区间缩小的情况,就ans先减,再更新cnt,因为要先消除贡献再减cnt,其它步骤类似就好了


参考代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;
typedef long long LL;
struct question
{
    int l,r,id;LL d;
}q[110000];
int belong[110000],block;
bool cmp1(question n1,question n2)
{
    if(belong[n1.l]==belong[n2.l]) return n1.r<n2.r;
    else return belong[n1.l]<belong[n2.l];
}
bool cmp2(question n1,question n2)
{
    return n1.id<n2.id;
}
LL ans;
int sum[110000];
LL cnt[110000];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        int d;
        scanf("%d",&d);
        sum[i]=sum[i-1]^d;
    }
    block=int(sqrt(n));
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
    for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+m+1,cmp1);
    memset(cnt,0,sizeof(cnt));
    int l=1,r=0;ans=0;
    for(int i=1;i<=m;i++)
    {
        while(r<q[i].r)
        {
            r++;
            cnt[sum[r]]++;
            ans+=cnt[sum[r]^k];
            if((sum[r]^k)==sum[l-1]) ans++;
        }
        while(r>q[i].r)
        {
            ans-=cnt[sum[r]^k];
            cnt[sum[r]]--;
            if((sum[r]^k)==sum[l-1]) ans--;
            r--;
        }
        while(l<q[i].l)
        {
            ans-=cnt[sum[l-1]^k];
            cnt[sum[l]]--;
            l++;
        }
        while(l>q[i].l)
        {
            l--;
            cnt[sum[l]]++;
            ans+=cnt[sum[l-1]^k];
        }
        q[i].d=ans;
    }
    sort(q+1,q+m+1,cmp2);
    for(int i=1;i<=m;i++) printf("%lld\n",q[i].d);
    return 0;
}

 

bzoj5301[CQOI2018]异或序列

bzoj5301[CQOI2018]异或序列

###题意 已知一个长度为 n 的整数数列 a[1],a[2],…,a[n] ,给定查询参数 l、r ,问在 [l,r] 区间内,有多少连续子 序列满足异或和等于 k 。 也就是说,对于所有的 x,y (l≤x≤y≤r),能够满足a[x]^a[x+1]^…^a[y]=k的x,y有多少组。 ###分析 这样的题目首先按照异或运算前缀和,就变成多次查询区间内有多少对数满足异或和为k. 考虑简单的情况.异或和为0的时候,就变成查询区间内有多少对数相同.这是很显然的莫队题目. 那么异或和为k的时候我们也可以考虑莫队.比较简明的思路是用trie树维护区间内所有的数字,加入/删除数字的时候更新答案. 另一种方式是,把所有的前缀和异或上k,组成另外一个数列,那么对于询问的区间在原先的前缀和数列和异或k的前缀和数列上都有一个数集,然后找两个数集的相同的数对(就是找这样的数对:数值相同,但是一个数在这个数集,另一个数在那个数集).

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
int a[maxn],s1[maxn],s2[maxn];
int cnt1[500000],cnt2[500000],ans;
int SZ=250;
struct query{
    int l,r,num,ans;
    void read(){
        scanf("%d%d",&l,&r);l--;
    }
}Q[maxn];
bool cmp1(const query &A,const query &B){
    if(A.l/SZ!=B.l/SZ)return A.l<B.l;
    return A.r<B.r;
}
void init(int l,int r){
    //printf("%d %d\n",l,r);
    
    for(int i=l;i<=r;++i){
        //printf("%d ",s1[i]);
        cnt1[s1[i]]++;
    }//printf("\n");
    for(int i=l;i<=r;++i){
        cnt2[s2[i]]++;//printf("%d ",s2[i]);
        ans+=cnt1[s2[i]];
    }//printf("\n");
    //printf("%d\n",ans);
}
void move(int l1,int r1,int l2,int r2){
    for(int i=l1;i<l2;++i){
        ans-=cnt2[s1[i]];ans-=cnt1[s2[i]];
        cnt1[s1[i]]--;cnt2[s2[i]]--;
        if(s1[i]==s2[i])ans+=1;
    }
    for(int i=l1-1;i>=l2;--i){
        ans+=cnt2[s1[i]];ans+=cnt1[s2[i]];
        cnt1[s1[i]]++;cnt2[s2[i]]++;
        if(s1[i]==s2[i])ans-=1;
    }
    for(int i=r1+1;i<=r2;++i){
        ans+=cnt2[s1[i]];ans+=cnt1[s2[i]];
        cnt1[s1[i]]++;cnt2[s2[i]]++;
        if(s1[i]==s2[i])ans-=1;    
    }
    for(int i=r1;i>r2;--i){
        ans-=cnt2[s1[i]];ans-=cnt1[s2[i]];
        cnt1[s1[i]]--;cnt2[s2[i]]--;
        if(s1[i]==s2[i])ans+=1;
    }
}
int res[maxn];
int main(){
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;++i){
        scanf("%d",a+i);
    }
    for(int i=1;i<=n;++i)s1[i]=s1[i-1]^a[i];
    for(int i=0;i<=n;++i)s2[i]=s1[i]^k;
    for(int i=1;i<=m;++i)Q[i].read();
    for(int i=1;i<=m;++i)Q[i].num=i;
    sort(Q+1,Q+m+1,cmp1);
    init(Q[1].l,Q[1].r);Q[1].ans=ans;
    for(int i=2;i<=m;++i){
        move(Q[i-1].l,Q[i-1].r,Q[i].l,Q[i].r);
        Q[i].ans=ans;
    }
    if(k==0){
        for(int i=1;i<=m;++i)Q[i].ans-=(Q[i].r-Q[i].l+1);
    }
    for(int i=1;i<=m;++i)Q[i].ans/=2;
    for(int i=1;i<=m;++i)res[Q[i].num]=Q[i].ans;
    for(int i=1;i<=m;++i)printf("%d\n",res[i]);
    return 0;
}

BZOJ5314: [Jsoi2018]潜入行动 (树形DP)

BZOJ5314: [Jsoi2018]潜入行动 (树形DP)

题意:一棵树选择恰好k个结点放置监听器 

   每个监听器只能监听相邻的节点

   问能使得所有节点被监听的种类数

题解:反正就是很well-known的树形DP了

   至于时间复杂度为什么是nk 不会不学

   很好想到四维dp 在x节点放置j个 x有没有放监听器 x有没有被监听

   瞎搞搞就好了 头写昏了转移就抄别人了的TAT

   关键是这个题开ll会mle 不开会爆int 取%频繁还会tle

   西安太热了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

 

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int n, k;

struct node
{
    int to, nex;
}E[200005];
int head[100005];
int sz[100005];
int dp[100002][102][2][2]; //放不放 被占用没
ll tmp[102][2][2];

inline void add(int &x,ll y)
{
    if(x + y >= mod) x = x + y - mod;
    else x = x + y;
}

void dfs(int x, int fa)
{
    sz[x] = 1;
    dp[x][1][1][0] = 1; //
    dp[x][0][0][0] = 1; //不放
    int c = head[x];
    for(int i = c; i; i = E[i].nex)
    {
        int v = E[i].to;
        if(v == fa) continue;
        dfs(v, x);

        int tx = min(k, sz[x]);
        int tv = min(k, sz[v]);
        for(int j = 0; j <= tx; j++)
            for(int jj = 0; jj < 2; jj++)
                for(int jjj = 0; jjj < 2; jjj++)
                    tmp[j][jj][jjj] = dp[x][j][jj][jjj], dp[x][j][jj][jjj] = 0;

        for(int j = 0; j <= tx; j++)
        {
            for(int jj = 0; jj <= tv && j + jj <= k; jj++)
            {
                add(dp[x][j + jj][0][0], tmp[j][0][0] * dp[v][jj][0][1] % mod);
                add(dp[x][j + jj][0][1], (tmp[j][0][0] * dp[v][jj][1][1] + tmp[j][0][1] * (dp[v][jj][0][1] + dp[v][jj][1][1]))%mod);
                add(dp[x][j + jj][1][0], tmp[j][1][0] * (dp[v][jj][0][0] + dp[v][jj][0][1]) % mod);
                add(dp[x][j + jj][1][1], (tmp[j][1][0] * (dp[v][jj][1][0] + dp[v][jj][1][1]) + tmp[j][1][1] * (dp[v][jj][0][0] + dp[v][jj][1][0]) + tmp[j][1][1] * (dp[v][jj][0][1] + dp[v][jj][1][1]))%mod);
            }
        }
        sz[x] += sz[v];
    }
}

int main()
{
    int cnt = 0;
    scanf("%d%d", &n, &k);
    for(int i = 1; i < n; i++)
    {
        int u, v; scanf("%d%d", &u, &v);
        E[++cnt].to = v; E[cnt].nex = head[u]; head[u] = cnt;
        E[++cnt].to = u; E[cnt].nex = head[v]; head[v] = cnt;
    }
    dfs(1, -1);
    printf("%lld\n", (dp[1][k][1][1] + dp[1][k][0][1]) % mod);
    return 0;
}
View Code

 

BZOJ5316 : [Jsoi2018]绝地反击

BZOJ5316 : [Jsoi2018]绝地反击

若$R=0$,那么显然答案为离原点最远的点到原点的距离。

否则若所有点都在原点,那么显然答案为$R$。

否则考虑二分答案$mid$,检查$mid$是否可行。

那么每个点根据对应圆交,可以覆盖圆上的一部分,每个可行方案都可以通过平移使得刚好卡住某个交点。

枚举每个交点,算出圆上$n$个位置的坐标,然后匈牙利算法判断是否存在完美匹配,时间复杂度$O(n^4\log w)$,不能承受。

 

注意到这个图是个稠密图,所以可以用bitset对匈牙利进行加速,做到$O(\frac{n^3}{32})$每次匹配。

另一方面,可以先枚举一个点$x$,然后再二分答案$mid$,判断是否有可行方案使得$x$刚好匹配$x$和圆的交点。

在这里,显然只需要在之前答案$ans$的基础之上往下二分,如果$ans-eps$不可行那么就没有继续二分的必要。

即:设$f[x]$表示$x$得到的最优解,若$f[x]$不是$f[1,x]$的最小值,那么就没有继续二分的必要。

考虑将读入的$n$个点随机打乱,那么$f[x]$是$f[1,x]$的最小值的概率为$\frac{1}{x}$,一共只有期望$O(\log n)$个$x$有二分的必要。

检查次数骤降为$O(n+\log n\log w)$,时间复杂度$O(\frac{(n+\log n\log w)n^3}{32})$。

 

注意要特判$mid$过小或者过大导致$x$与圆没有交点的情况。

 

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef unsigned int U;
const int N=205,M=7;
const double eps=1e-9,pi=acos(-1.0);
int n,m,R,i,j;double lim,ans,rot[N][2],b[N][2];
int f[N];U v[M],g[N][M];
struct P{int x,y;}a[N];
inline int sgn(double x){
  if(x>eps)return 1;
  if(x<-eps)return -1;
  return 0;
}
bool find(int x){
  for(int i=0;i<=m;i++){
    U t=v[i]&g[x][i];
    while(t){
      int j=i<<5|__builtin_ctz(t);
      v[i]^=1U<<(j&31);
      if(f[j]<0||find(f[j]))return f[j]=x,1;
      t-=t&-t;
    }
  }
  return 0;
}
inline bool check(int A,int B,double C){
  double d=sqrt(a[A].x*a[A].x+a[A].y*a[A].y);
  double l=(a[A].x*a[A].x+a[A].y*a[A].y+C*C-R*R)/(2*d);
  double h=sqrt(max(C*C-l*l,0.0));
  double bx=-a[A].x/d,by=-a[A].y/d;
  double px=a[A].x+bx*l,py=a[A].y+by*l;
  by=-by;
  swap(bx,by);
  bx*=h,by*=h;
  if(B==0)px+=bx,py+=by;else px-=bx,py-=by;
  int i,j;
  b[0][0]=px,b[0][1]=py;
  for(i=1;i<n;i++){
    b[i][0]=px*rot[i][1]-py*rot[i][0];
    b[i][1]=px*rot[i][0]+py*rot[i][1];
  }
  C*=C;
  for(i=0;i<n;i++){
    for(j=0;j<=m;j++)g[i][j]=0;
    for(j=0;j<n;j++)if(sgn((a[i].x-b[j][0])*(a[i].x-b[j][0])+(a[i].y-b[j][1])*(a[i].y-b[j][1])-C)<=0)g[i][j>>5]|=1U<<(j&31);
  }
  for(i=0;i<n;i++)f[i]=-1;
  for(i=0;i<n;i++){
    for(j=0;j<=m;j++)v[j]=~0U;
    if(!find(i))return 0;
  }
  return 1;
}
int main(){
  scanf("%d%d",&n,&R);
  m=(n-1)>>5;
  for(i=0;i<n;i++)scanf("%d%d",&a[i].x,&a[i].y);
  if(!R){
    int ans=0;
    for(i=0;i<n;i++)ans=max(ans,a[i].x*a[i].x+a[i].y*a[i].y);
    double ret=sqrt(ans);
    return printf("%.15f",ret),0;
  }
  random_shuffle(a,a+n);
  for(i=1;i<n;i++){
    double o=pi*2*i/n;
    rot[i][0]=sin(o),rot[i][1]=cos(o);
  }
  for(i=0;i<n;i++){
    int t=a[i].x*a[i].x+a[i].y*a[i].y;
    double val;
    if(t<=R)val=R-sqrt(t);else val=sqrt(t)-R;
    lim=max(lim,val);
    ans=max(ans,sqrt(t)+R);
  }
  for(i=0;i<n;i++)if(a[i].x||a[i].y)for(j=0;j<2;j++){
    double l=lim,r=max(min(sqrt(a[i].x*a[i].x+a[i].y*a[i].y)+R,ans-eps),lim);
    if(!check(i,j,r))continue;
    while(l+eps<r){
      double mid=(l+r)/2;
      if(check(i,j,mid))r=ans=mid;else l=mid;
    }
  }
  return printf("%.15f",ans),0;
}

  

我们今天的关于【BZOJ5316】[JSOI2018]绝地反击网络流,计算几何,二分的分享就到这里,谢谢您的阅读,如果想了解更多关于BZOJ5301: [Cqoi2018]异或序列、bzoj5301[CQOI2018]异或序列、BZOJ5314: [Jsoi2018]潜入行动 (树形DP)、BZOJ5316 : [Jsoi2018]绝地反击的相关信息,可以在本站进行搜索。

本文标签: