10.28多校赛后总结

考试复现

8:00~8:15 看题。

8:15~8:40 写T1。

8:40~10:04 写T2,画的时间有点长,想到过正解的思路,但是无法证明是否正确,就没有继续想。

10:04~12:00 由于T3的题面太长,并且看不懂,所以写T4。

A. 数论入门

        起初的思路是先预处理处k以内的所有质数,再枚举是k的因子的质数在k中的个数,答案时所有质因子的个数除以二向上取整次方的乘积。但是五星数学家麦克阿瑟曾经说过:“我花了一晚上用欧拉筛求1e12以内质数,不是我求出来了,而是天亮了。”会超时。所以转变我方向,如果k是平方数的话,那么答案就是\sqrt{k},否则答案肯定是大于\sqrt{k}且小于k的一个数,那么我就可以从\sqrt{k}到2枚举(2<=i<=\sqrt{k}),看k可以除几个(k/=i),num(初值为1)就乘几个i,若num=1,输出-1,否则输出k/num;

#include<bits/stdc++.h>
using namespace std;
long long n,ans=1;
int main()
{
	scanf("%lld",&n);
	long long num=n;
	for(long long i=sqrt(n);i>=2;--i)
	{
		while(num%(i*i)==0)
		{
			ans*=i;
			num/=i*i;
		}
	}
	if(ans==1) cout<<-1<<endl;
	else cout<<n/ans<<endl;
	return 0;
}

B. 删库跑路

        前20分码暴力,复杂度O(n^{2}),枚举n个点的删除顺序。

        后30分v_i=1,用单调队列存每个点的入度,入度小的先删除,入度大的后删除。

正解:

        考虑一条边被删除当且仅当这条边两端点之一被删除。假设这条边连接x、y,那么这条边带来的贡献要么是删x带来v_y,要么是删y带来v_x​。也就是说,\sum _{i=1}^{m}min(v_x,x_y)一定是答案的一个下界,答案不可能比它小。考虑能否构造出一个删除方案使得答案等于它。给每一条无向边赋方向:让点权大的点指向点权小的点。若两点点权相同,让编号大的点指向编号小的点。这样构造出的有向图一定是一个DAG。跑出来的拓扑序就是删点的顺序。所以,证明了一定可以构造出一个删点方案,使得\sum _{i=1}^{m}min(v_x,x_y)。在读入的时候加一下即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int head[N],last[N*4],to[N*4],tot;
int n,m,x,y,du[N],vs[N];
long long v[N],num[N],ans;
void add(int x,int y)
{
	to[++tot]=y;
	last[tot]=head[x];
	head[x]=tot;
}
priority_queue<pair<int,int> >q;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%lld",&v[i]);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&x,&y);
		if(v[x]>v[y]||v[x]==v[y]&&x<y) add(x,y),du[y]++;
		else add(y,x),du[x]++;
		num[x]+=v[y],num[y]+=v[x];
	}
	for(int i=1;i<=n;++i) q.push(make_pair(-du[i],i));
	int t=0;
	while(t<n)
	{
		int id=q.top().second;
		q.pop();
		t++;
		ans+=num[id];
		vs[id]=1;
		for(int i=head[id];i;i=last[i])
		{
			int y=to[i];
			if(vs[y]) continue;
			num[y]-=v[id];
			du[y]--;
			q.push(make_pair(-du[y],y));
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C. -1+i进制系统

        考试的时候没写,原因其一是题目太长了,看晕了,其二是虚数这一块每看明白,不过同学一讲后就明白了,这不就是个数学题吗,简单的不行。

正解:

        

        把公式拆解后写个dfs就行了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
string s;
int f1=1,f2=1,a,b;
bool num=false;
stack<int> ans;
void dfs(int a,int b)
{
	if(a==0&&b==0) return;
	int r=abs(a-b)%2;
	ans.push(r);
	dfs((b-a+r)/2,(-b-a+r)/2);
}
signed main()
{
	cin>>s;
	int po=1,ff=1,tot=-1;
	for(int i=s.size()-1;i>=0;--i)
	{
		if(s[i]=='i')
		{
			ff=0;
			continue;
		}
		if(ff) continue;
		if(s[i]=='-')
		{
			f2=-1;
			tot=i-1;
			break;
		}
		else if(s[i]=='+')
		{
			tot=i-1;
			break;
		}
		b+=(s[i]-'0')*po;
		po*=10;
	}
	if(ff) tot=s.size()-1;
	for(int i=0;i<=tot;++i)
	{
		if(s[i]=='-') f1=-1;
		else a=a*10+s[i]-'0'; 
	}
	a*=f1,b*=f2;
	dfs(a,b);
	while(!ans.empty())
	{
		cout<<ans.top();
		ans.pop();
	}
	return 0;
}

D. 醉醉疯疯渺渺空空

        不妨令1为根,则如果x和y的lca并非xy之一,则比较好实现,维护f[x]表示x到它的儿子们的价值之和,f[x]=1+\sum f[y]*2,f,y是x的儿子,d[x]表示x的深度,则答案为f[x]*f[y]*2^{dis(x,y)}。不妨设x是y的祖先,则我们可以维护g[x]表示x的父亲,距离除了x之外的子树的点和祖先的价值之和,换根dp维护之即可。我们先跑一遍dfs预处理出f[x],再跑第二遍时,不妨假设x的父亲已经算好了,则x的g[x]为g[fa[x]]*2+\sum f[bro] * 4,这些可以递归地进行。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5,mod=998244353;
int read()
{
    int num=0;
	bool flag=1;
    char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
      if(c=='-')flag=0;
    for(;c>='0'&&c<='9';c=getchar())
      num=(num<<1)+(num<<3)+c-'0';
    return flag?num:-num;
}
int head[N],last[N<<1],to[N<<1],tot;
void add(int x,int y)
{
	to[++tot]=y;
	last[tot]=head[x];
	head[x]=tot;
}
int n,m,x,y,f[N],ff[N][25],deep[N],prime[N];
void get_way(int x,int fat)
{
	f[x]=1;
	deep[x]=deep[fat]+1;
	for(int i=0;i<=19;i++) ff[x][i+1]=ff[ff[x][i]][i];
	for(int i=head[x];i;i=last[i])
	{
		int y=to[i];
		if(y==fat) continue;
		ff[y][0]=x;
		get_way(y,x);
		f[x]=(f[x]+f[y]*2%mod)%mod;
	}
}
int LCA(int x,int y)
{
	if(deep[y]>deep[x]) swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(deep[ff[x][i]]>=deep[y]) x=ff[x][i];
		if(x==y) return x;
	}
	for(int i=20;i>=0;i--)
	{
		if(ff[x][i]!=ff[y][i])
		{
			x=ff[x][i];
			y=ff[y][i];
		}
	}
	return ff[x][0];
}
int g[N];
void get_g(int x,int nn)
{
	g[x]=nn;
	for(int i=head[x];i;i=last[i])
	{
		int y=to[i];
		if(ff[x][0]==y) continue;
		get_g(y,(g[x]*2%mod+(f[x]-f[y]*2%mod+mod)%mod*2%mod)%mod);
	}
}
int get_fa(int y,int x)
{
	for(int i=20;i>=0;--i)
		if(deep[ff[y][i]]>=x) y=ff[y][i];
	return y;
}
signed main()
{
	n=read();
	for(int i=1;i<n;++i)
	{
		x=read(),y=read();
		add(x,y),add(y,x);
	}
	prime[0]=1;
	for(int i=1;i<=n;++i) prime[i]=prime[i-1]*2%mod;
	m=read();
	get_way(1,0);
	get_g(1,0);
	for(int i=1;i<=m;++i)
	{
		x=read(),y=read();
		int lca=LCA(x,y);
		if(lca!=x&&lca!=y) printf("%lld\n",prime[deep[x]+deep[y]-2*deep[lca]]*f[x]%mod*f[y]%mod);
		else
		{
			if(deep[x]<deep[y]) swap(x,y);
			int xx=get_fa(x,deep[y]+1);
			printf("%lld\n",((g[y]+f[y]-2*f[xx])%mod+mod)%mod*f[x]%mod*prime[deep[x]-deep[y]]%mod);
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值