Wannafly挑战赛4 解题报告

A.解方程

题目大意:给出n个整数和x,请问这n个整数中是否存在三个数a,b,c使得ax2+bx+c=0,数字可以重复使用

简要题解:枚举a和b,判断是否存在c。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<map>

using namespace std;

int a[1010];
int n,x;
map<int,int> mp;

int main()
{
	scanf("%d%d",&n,&x);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),mp[-a[i]]=1;
	bool w=0;
	for (int i=1;i<=n && !w;i++)
	  for (int j=1;j<=n && !w;j++)
	    if (mp[a[i]*x*x+a[j]*x]) w=1;
	if (w) puts("YES"); else puts("NO");
	return 0;
}


B.小AA的数列

题目大意:给定一个长度为n的数列,求所有长度为偶数且在[L,R]之内的区间异或值之和。

简要题解:问题可以转化为求长度为偶数且不超过R的答案。按位枚举,从前向后扫,分别记录当前奇数/偶数位置的0/1有多少个,即可计算出以当前位置为右端点的区间的答案。当长度大于R时,则删除左端点。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 100010
#define mod 1000000007

using namespace std;

int a[maxn],b[2][2],c[maxn];
long long ans;
int n,L,R;

long long calc(int now,int len)
{
	memset(b,0,sizeof(b));
	long long ans=0;
	c[0]=0;for (int i=1;i<=n;i++) if (a[i]&(1ll<<now)) c[i]=1; else c[i]=0;
	b[0][0]=1;
	for (int i=1;i<=n;i++)
	{
		if (i>len) b[(i-len-1)&1][c[i-len-1]]--;
		ans+=b[i&1][!c[i]];
		b[i&1][c[i]]++;
	}
	return ans%mod;
}

long long calc(int len)
{
	long long ans=0;
	for (int i=0;i<31;i++) ans+=1ll*(1ll<<i)*calc(i,len)%mod;
	return ans%mod;
}

int main()
{
	scanf("%d%d%d",&n,&L,&R);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]^=a[i-1];
	printf("%lld\n",(calc(R)-calc(L-1)+mod)%mod);
	return 0;
}


C.割草机

题目大意:

n*m的01矩阵,从(1,1)出发,面向(1,m),可以按如下两种方式移动,均消耗1单位时间:
1、向面朝的方向移动一格
2、向下移动一格,并反转面朝的方向(右变左,左变右)
求遍历所有1的最少时间

简要题解:奇数行都是从左向右走,偶数行都是从右向左走。每一行都是从最左端的1走到最右端的1,模拟一下就可以。注意处理中间的空行。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>

using namespace std;

char s[210];
int n,m,mx[210],mn[210],N;
int ans,now;

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		mx[i]=0;mn[i]=m+1;
		for (int j=1;j<=m;j++) if (s[j]=='W') mn[i]=min(mn[i],j),mx[i]=max(mx[i],j);
	}
	ans=mx[1]-1;now=mx[1];
	for (int i=1;i<n;i++)
	{
		if (mx[i+1]<mn[i+1]) ans++;
		else
		{
			if (i&1) ans+=2*max(mx[i+1],now)-now-mn[i+1]+1,now=mn[i+1];
			else ans+=mx[i+1]+now-2*min(mn[i+1],now)+1,now=mx[i+1];
		}
	}
	for (int i=n;i>=1;i--) if (mx[i]<mn[i]) ans--; else break;
	printf("%d\n",ans);
	return 0;
}


D.树的距离

题目大意:给定一棵n个节点的树,m组询问,每次询问x子树内,与x距离大于等于k的点到x的距离之和。

简要题解:巧的是和昨天CF的F题撞了,又恰巧我在比赛前的那个下午做了那道F题,于是成功拿到一血。x与x子树内的节点距离可转化为深度之差,故可以将深度离散化。按深度升序排序,建主席树,下标为dfs序,维护dep之和。本应查询的是query(root[dep[x]+k],in[x],out[x])-query(root[dep[x]-1],in[x],out[x]),但可以发现x子树内的点dep值都大于x,故后一项为0,直接询问前缀即可。(好像比原题弱化了)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define N 4001000
#define maxn 200010

using namespace std;

int head[maxn],len[maxn],nxt[maxn],to[maxn],root[maxn];
long long dep[maxn],b[maxn],sum[N];
int lch[N],rch[N],cnt[N];
int n,T,num,tot,in[maxn],out[maxn],m;
vector<int> v[maxn];

void addedge(int x,int y,int z)
{
	num++;to[num]=y;len[num]=z;nxt[num]=head[x];head[x]=num;
}

void dfs(int x)
{
	in[x]=++tot;
	for (int p=head[x];p;p=nxt[p]) dep[to[p]]=dep[x]+len[p],dfs(to[p]);
	out[x]=tot;
}

int modify(int pre,int l,int r,int pos,long long d)
{
	int now=++tot;
	if (l==r) sum[now]=sum[pre]+d,cnt[now]=cnt[pre]+1;
	else
	{
		int mid=(l+r)>>1;
		if (pos<=mid)
		{
			lch[now]=modify(lch[pre],l,mid,pos,d);rch[now]=rch[pre];
		}
		else
		{
			rch[now]=modify(rch[pre],mid+1,r,pos,d);lch[now]=lch[pre];
		}
		sum[now]=sum[lch[now]]+sum[rch[now]];
		cnt[now]=cnt[lch[now]]+cnt[rch[now]];
	}
	return now;
}

long long query_sum(int x,int l,int r,int L,int R)
{
	if (L<=l && r<=R) return sum[x];
	int mid=(l+r)>>1;
	long long ans=0;
	if (L<=mid) ans+=query_sum(lch[x],l,mid,L,R);
	if (mid<R) ans+=query_sum(rch[x],mid+1,r,L,R);
	return ans;
}

int query_cnt(int x,int l,int r,int L,int R)
{
	if (L<=l && r<=R) return cnt[x];
	int mid=(l+r)>>1,ans=0;
	if (L<=mid) ans+=query_cnt(lch[x],l,mid,L,R);
	if (mid<R) ans+=query_cnt(rch[x],mid+1,r,L,R);
	return ans;
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<n;i++)
	{
		int x,z;
		scanf("%d%d",&x,&z);
		addedge(x,i+1,z);
	}
	dfs(1);
	tot=0;
	for (int i=1;i<=n;i++) b[i]=dep[i];
	sort(b+1,b+n+1);b[n+1]=1e12;
	m=unique(b+1,b+n+2)-b-1;
	for (int i=1;i<=n;i++) v[lower_bound(b+1,b+m+1,dep[i])-b].push_back(i);
	for (int i=1;i<=m;i++)
	{
		root[i]=root[i-1];
	    for (int j=0;j<v[i].size();j++)
	      root[i]=modify(root[i],1,n,in[v[i][j]],dep[v[i][j]]);
	}
	scanf("%d",&T);
	while (T--)
	{
		int x,k;
		scanf("%d%d",&x,&k);
		int A=lower_bound(b+1,b+m+1,dep[x]+k)-b-1;
		long long sum=query_sum(root[m],1,n,in[x],out[x])-query_sum(root[A],1,n,in[x],out[x]);
		long long cnt=query_cnt(root[m],1,n,in[x],out[x])-query_cnt(root[A],1,n,in[x],out[x]);
		printf("%lld\n",sum-cnt*dep[x]);
	}
	return 0;
}


E.方程的解

题目大意:T组询问,判断模p意义下方程x^2+ax+b=0是否有整数解,其中p为奇质数。

简要题解:配方后,可看做x^2=a是否有解,套板子即可。注意特判p=2的情况。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <algorithm>  
#include <iostream>  
#include <math.h>  
  
using namespace std;  
typedef long long LL;  
  
LL quick_mod(LL a, LL b, LL m)  
{  
    LL ans = 1;  
    a %= m;  
    while(b)  
    {  
        if(b & 1)  
        {  
            ans = ans * a % m;  
            b--;  
        }  
        b >>= 1;  
        a = a * a % m;  
    }  
    return ans;  
}  
  
struct T  
{  
    LL p, d;  
};  
  
LL w;  
  
//二次域乘法  
T multi_er(T a, T b, LL m)  
{  
    T ans;  
    ans.p = (a.p * b.p % m + a.d * b.d % m * w % m) % m;  
    ans.d = (a.p * b.d % m + a.d * b.p % m) % m;  
    return ans;  
}  
  
//二次域上快速幂  
T power(T a, LL b, LL m)  
{  
    T ans;  
    ans.p = 1;  
    ans.d = 0;  
    while(b)  
    {  
        if(b & 1)  
        {  
            ans = multi_er(ans, a, m);  
            b--;  
        }  
        b >>= 1;  
        a = multi_er(a, a, m);  
    }  
    return ans;  
}  
  
//求勒让德符号  
LL Legendre(LL a, LL p)  
{  
    return quick_mod(a, (p-1)>>1, p);  
}  
  
LL mod(LL a, LL m)  
{  
    a %= m;  
    if(a < 0) a += m;  
    return a;  
}  
  
LL Solve(LL n,LL p)  
{  
	if (n==0) return 0;
    if(p == 2) return 1;  
    if (Legendre(n, p) + 1 == p)  
        return -1;  
    LL a = -1, t;  
    while(true)  
    {  
        a = rand() % p;  
        t = a * a - n;  
        w = mod(t, p);  
        if(Legendre(w, p) + 1 == p) break;  
    }  
    T tmp;  
    tmp.p = a;  
    tmp.d = 1;  
    T ans = power(tmp, (p + 1)>>1, p);  
    return ans.p;  
}  
  
int main()  
{  
    int t;  
    scanf("%d", &t);  
    while(t--)  
    {  
        int a,b,p;  
        scanf("%d%d%d",&a,&b,&p);  
        if (p==2)
        {
        	if (a==0 && b==0) puts("Yes");
        	if (a==1 && b==0) puts("Yes");
        	if (a==0 && b==1) puts("Yes");
        	if (a==1 && b==1) puts("No");
        	continue;
        }
        int n = ((1ll*a*a-4ll*b)%p+p)%p;  
        int x = Solve(n, p);  
        if(x == -1) puts("No");
		else puts("Yes");   
    }  
    return 0;  
}  

F.线路规划

题目大意:n个点的树(树边不是通信线路),有m条通信线路,通信线路(x,y,k,w)表示x--y,fa[x]--fa[y],fa[fa[x]]--fa[fa[y]],……,x的k-1级父亲--y的k-1级父亲,这k条边,每条边的费用为m。求最大联通块大小为多少,在最大联通块大小尽量大的条件下,至少要花多少钱。

简要题解:多校2017某题与SCOI2016萌萌哒的合体版。最小生成树的思路,先按照边权升序排序,加入边。考虑如何快速合并,f[j][i]表示从i开始2^j个祖先的匹配情况,每次合并可用RMQ的思想,把大区间拆成两个重叠的区间(因为合并操作可以重叠),若可以把f[k][x]与f[k][y]合并,则把这个区间拆成2个重叠区间,merge(x,y,k-1),merge(fa[k-1][x],fa[k-1][y],k-1)。对于每一个f[j][i]最多被合并一次,所以总复杂度O(n log n),而倍增又可以快速判断两个区间是否需要合并,所以问题得到完美解决。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 300010

using namespace std;

struct edge
{
	int x,y,k,w;
}e[maxn];

int head[maxn],to[maxn],nxt[maxn];
int dep[maxn],size[maxn],fa[20][maxn],f[20][maxn];
long long ans[maxn];
int n,K,m,num;

void addedge(int x,int y)
{
	num++;to[num]=y;nxt[num]=head[x];head[x]=num;
}

void dfs(int x)
{
	for (int p=head[x];p;p=nxt[p]) dep[to[p]]=dep[x]+1,dfs(to[p]);
}

int go_up(int x,int d)
{
	for (int i=0;i<=K;i++) if (d&(1<<i)) x=fa[i][x];
	return x;
}

bool cmp(edge x,edge y) {return x.w<y.w;}

int find(int k,int x)
{
	if (f[k][x]==x) return x;
	return f[k][x]=find(k,f[k][x]);
}

void merge(int x,int y,int k,int w)
{
	int f1=find(k,x),f2=find(k,y);
	if (f1!=f2)
	{
		f[k][f1]=f2;
		if (!k) {size[f2]+=size[f1],ans[f2]+=ans[f1]+w;return;}
		merge(x,y,k-1,w);
		merge(fa[k-1][x],fa[k-1][y],k-1,w);
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	K=floor(log(n)/log(2.0));
	for (int i=1;i<=n;i++) f[0][i]=i,size[i]=1;
	for (int i=2;i<=n;i++) {scanf("%d",&fa[0][i]);addedge(fa[0][i],i);}
	dfs(1);
	for (int j=1;j<=K;j++)
	  for (int i=1;i<=n;i++)
	    fa[j][i]=fa[j-1][fa[j-1][i]],f[j][i]=i;
	for (int i=1;i<=m;i++) scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].k,&e[i].w);
	sort(e+1,e+m+1,cmp);
	for (int i=1;i<=m;i++)
	{
		int k=floor(log(e[i].k)/log(2.0));
		merge(e[i].x,e[i].y,k,e[i].w);
		merge(go_up(e[i].x,e[i].k-(1<<k)),go_up(e[i].y,e[i].k-(1<<k)),k,e[i].w);
	}
	int ANS=0;
	for (int i=1;i<=n;i++)
	  if (f[0][i]==i && size[i]>size[ANS] || (size[i]==size[ANS] && ans[i]<ans[ANS])) ANS=i;
	printf("%d %lld\n",size[ANS],ans[ANS]);
	return 0;
}


总结:很可惜,差两名拿到奖金。输在罚时上,真的很气。B题没有看到取模,没有删调试导致挂了3次。C题数组开小了又挂了一次。D题犯了一个很蠢的错误,挂了一次。E题没有考虑2的情况,挂了一次。应该说,除了E之外,这些错误基本都可以避免,所以拿不到奖金还是要反思的。比较开心的是,凭借下午刚码的主席树抢到了D的一血。F题是道好题,但是如果会做SCOI那道题,这道题应该很容易就想出来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值