noip冲刺赛第五次考试
1.公约数 (gcd.cpp\c\pas)
【问题描述】 给定一个正整数,在[1,n]的范围内,求出有多少个无序数对(a,b)满足 gcd(a,b)=a xor b。
【输入格式】 输入共一行,一个正整数n。
【输出格式】 输出共一行,一个正整数表示答案。
【输入输出样例】 gcd.in 3
gcd.out 1
解释:只有(2,3)满足要求
【数据范围】 对于30%的数据满足n<=1000 对于60%的数据满足n<=10^5 对于100%的数据满足n<=10^7
拿到题就打了30分版本,没推出来公式。
60分版本:
设a^b=c,所以可得a^c=b;
-->gcd(a,a^c)=c;
然后通过枚举a,c完成代码
复杂度O(nlog(n^2))
100分版本:
在60分版本上,加上以下公式:
首先,a=b肯定无解,不妨设a>b:
1:gcd(a,b)是a,b的因数,a-b中也有gcd(a,b)这个因数--->a-b>=gcd(a,b);
2:显然,a^b>=a-b,因为异或不同的地方为1,而减法会牵扯借位;
所以得到gcd(a,b)<=a-b<=a^b;
因为gcd(a,b)=a^b
所以a^b=a-b=gcd(a,b)=c;
b=a-c;
所以只需判断a^c是否等于a-c即可
枚举c,a=i*c
复杂度O(nlog(n))
#include<cstdio> int main() { int n,ans=0; scanf("%d",&n); for(int i = 1 ; i <= n ; ++i) for(int j = n/i ; j >= 2 ; --j) { int a=j*i; if((i^a)==(a-i))++ans; } printf("%d",ans); return 0; }
2.通讯 (message.cpp\c\pas)
【问题描述】“这一切都是命运石之门的 选择。” 试图研制时间机器的机关 SERN 截获了中二科学家伦太郎发往过去的一条 短信,并由此得知了伦太郎制作出了电话微波炉(仮)。 为了掌握时间机器的技术,SERN 总部必须尽快将这个消息通过地下秘密通讯 网络,传达到所有分部。 SERN 共有 N 个部门(总部编号为 0),通讯网络有 M 条单向通讯线路,每条 线路有一个固定的通讯花费 Ci。 为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向 另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所 有部门传递消息的费用和。 幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法 将信息由 X 传递到 Y,同时能由 Y 传递到 X),我们就可以忽略它们之间的花费。 由于资金问题(预算都花在粒子对撞机上了),SERN 总部的工程师希望知道, 达到目标的最小花费是多少。
【输入格式】多组数据,文件以 2 个 0 结尾。 每组数据第一行,一个整数 N,表示有 N 个包括总部的部门(从 0 开始编号)。 然后是一个整数 M,表示有 M 条单向通讯线路。 接下来 M 行,每行三个整数,Xi,Yi,Ci,表示第 i 条线路从 Xi 连向 Yi,花费 为 Ci。
【输出格式】 每组数据一行,一个整数表示达到目标的最小花费。
【数据范围】对于 10%的数据, 保证 M=N-1 对于另 30%的数据,N ≤ 20 ,M ≤ 20 对于 100%的数据,N ≤ 50000 , M ≤ 10^5 ,Ci ≤ 10^5 ,数据组数 ≤ 5 数据保证一定可以将信息传递到所有部门。
Tarjan_mod或者Kosaraju_mod都能过,裸的强连通分量。
1:Kosaraju
#include<cstdio> #include<algorithm> #include<cstring> #define maxm 100010 #define maxn 50010 #define inf 1<<29 int belong[maxn],head[maxn],head2[maxn],vis[maxn],dfn[maxn],dis[maxn]; int ecnt,cnt,sum,n,m,ans; using namespace std; struct edge{ int u,v,next,w; }E[maxm],Ee[maxm]; void addedge(int u,int v,int w) { E[++ecnt].u=u; E[ecnt].v=v; E[ecnt].w=w; E[ecnt].next=head[u]; head[u]=ecnt; Ee[ecnt].u=v; Ee[ecnt].v=u; Ee[ecnt].w=w; Ee[ecnt].next=head2[v]; head2[v]=ecnt; } void dfs(int x) { for(int i = head[x] ; i ; i = E[i].next) { int v=E[i].v; if(!vis[v]) { vis[v]=1; dfs(v); } } dfn[++cnt]=x; return ; } void search(int x) { for(int i = head2[x] ; i ; i = Ee[i].next) { int v=Ee[i].v; if(!belong[v]) { belong[v]=sum; search(v); } } return ; } void kasa() { cnt=0; for(int i = 1 ; i <= n ; ++i) if(!vis[i]) { vis[i]=1; dfs(i); } for(int i = cnt ; i >= 1 ; --i) { int x=dfn[i]; if(!belong[x]) { belong[x]=++sum; search(x); } } } void init() { ecnt=cnt=sum=ans=0; memset(vis,0,sizeof(vis)); memset(dfn,0,sizeof(dfn)); memset(belong,0,sizeof(belong)); memset(head,0,sizeof(head)); memset(head2,0,sizeof(head2)); for(int i = 1 ; i <= maxn ; ++i)dis[i]=inf; } int main() { int a,b,w; while(scanf("%d%d",&n,&m)&&n&&m) { init(); for(int i = 1 ; i <= m ; ++i) { scanf("%d%d%d",&a,&b,&w); addedge(a+1,b+1,w); } kasa(); for(int i = 1 ; i <= m ; ++i) { for(int j = head[i] ; j ; j = E[j].next) { int u=E[j].u; int v=E[j].v; int w=E[j].w; if(belong[u]!=belong[v])dis[belong[v]]=min(dis[belong[v]],w); } } for(int i = 1 ; i <= sum ; ++i) { if(dis[i]==inf)dis[i]=0; ans+=dis[i]; } printf("%d\n",ans); } return 0; }
2、Tarjan
#include<cstdio> #include<algorithm> #include<cstring> #define maxn 100010 #define maxm 50010 #define inf 1<<29 using namespace std; int instack[maxm],stack[maxm],belong[maxm],dfn[maxm],low[maxm],head[maxm],dis[maxm]; int ecnt,cnt,ans,ord,top; struct edge{ int u,v,next,w; }E[maxn]; void addedge(int u,int v,int w) { E[++ecnt].u=u; E[ecnt].v=v; E[ecnt].w=w; E[ecnt].next=head[u]; head[u]=ecnt; } void Tarjan(int x) { dfn[x]=low[x]=++ord; stack[++top]=x; instack[x]=1; for(int i = head[x] ; i ; i = E[i].next) { int v=E[i].v; if(!dfn[v])Tarjan(v); if(instack[v])low[x]=min(low[v],low[x]); } if(dfn[x]==low[x]) { ++cnt; int v; do{ v=stack[top--]; instack[v]=0; belong[v]=cnt; }while(x!=v); } } void init() { top=ecnt=cnt=ans=ord=0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(E,0,sizeof(E)); memset(belong,0,sizeof(belong)); memset(head,0,sizeof(head)); memset(instack,0,sizeof(instack)); memset(stack,0,sizeof(stack)); for(int i = 1 ; i <= maxm ; ++i)dis[i]=inf; } int main() { int n,m,a,b,w; while(scanf("%d%d",&n,&m)&&n&&m) { init(); for(int i = 1 ; i <= m ; ++i) { scanf("%d%d%d",&a,&b,&w); addedge(a+1,b+1,w); } for(int i = 1 ; i <= n ; ++i) if(!dfn[i])Tarjan(i); for(int i = 1 ; i <= n ; ++i) for(int j = head[i] ; j ; j = E[j].next) { int v=E[j].v; int w=E[j].w; if(belong[i]!=belong[v])dis[belong[v]]=min(dis[belong[v]],w); } for(int i = 1 ; i <= cnt ; ++i) { if(dis[i]==inf)dis[i]=0; ans+=dis[i]; } printf("%d\n",ans); } return 0; }
3.label (label.cpp/c/pas)
【问题描述】 Samjia和Peter不同,他喜欢玩树。所以Peter送给他一颗大小为n的树,节 点编号从1到n。 Samjia要给树上的每一个节点赋一个[1,m]之间的权值,并使得有边直接相 连的两个节点的权值之差的绝对值 ≥ k。请你告诉Samjia有多少种不同的赋值 方案,只用求出答案对10 9+7(1000000007)取模得到的结果。
【输入格式】 输入文件名为 label.in。 输入数据的第一行包含一个整数 T,代表测试数据组数。 接下来是 T 组数据. 每组数据的第一行包含三个整数 n、m 和 k。 接下来 n − 1 行,每行包含两个整数 u 和 v, 代表节点 u 和 v 之间有 一条树边。
【输出格式】 输出文件名为 label.out。 对于每组数据,输出一行,包含一个整数,代表所求的答案。
树形DP(mmp...)
注释写在代码里
#include<cstdio> #include<algorithm> #include<cstring> #define ll long long #define maxn 105 #define maxm 10005 #define mod 1000000007 using namespace std; int f[maxn][maxm]; int T,n,m,k,lim,ecnt; int head[maxn]; struct edge{ int u,v,next; }E[maxn<<1]; void addedge(int u,int v)//邻接表 { E[++ecnt].u=u; E[ecnt].v=v; E[ecnt].next=head[u]; head[u]=ecnt; } ll getsum(int x,int from) { ll ret(0); for(int i = from;i <= lim ; ++i)ret=(ret+f[x][i]%mod)%mod;遍历dp左端情况 for(int i = m ; i >= m-lim+1 ; --i)//遍历dp右端情况 { if(i<=lim||i<from)break;//防止i左移超出dp右端范围 ret=(ret+f[x][m-i+1]%mod)%mod;//加上右端对应的dp左端值 } int l=max(from,lim+1),r=m-lim;//max是为了处理from大于lim+1的情况 int flg=r-l+1; if(flg>0)ret=(ret+1ll*flg*f[x][lim]%mod)%mod;//中间所有值相同,存在lim里即可 return ret; } void dfs(int u,int fa) { for(int i = 1 ; i <= lim ; ++i)f[u][i]=1; for(int i = head[u] ; i ; i = E[i].next) { int v= E[i].v; if(v==fa)continue; dfs(v,u);//建树 ll sum=getsum(v,k+1);//对于父亲节点选1的情况进行初始化,通过右移过程中减去右端的值,增加左端的值实现动态变化。 for(int j = 1 ; j <= lim ; ++j) { if(j-k>=1)sum=(sum+f[v][j-k]%mod)%mod;//加上新加入的左端值 f[u][j]=1ll*sum*f[u][j]%mod;//乘法原理 if(j+k<=m)//右端不出界 { int bb=j+k; if(m-bb+1<=lim)bb=m-bb+1;//右端对应到lim左边,把右端对应到左端对称位置。 else if(bb>=lim)bb=lim;右端对应到lim右边,把右端转移至lim位置 sum=(sum-f[v][bb]+mod)%mod;//减去右边进入k范围内的值 } } } } int ksm(int x,int y) { int a=x%mod; int ret(1); while(y) { if(y&1)ret=1ll*ret*a%mod; y>>=1; a=1ll*a*a%mod; } return ret; } int main() { int a,b; scanf("%d",&T); while(T--) { ecnt=0; memset(head,0,sizeof(head)); scanf("%d%d%d",&n,&m,&k); for(int i = 1 ; i < n ; ++i) { scanf("%d%d",&a,&b); addedge(a,b); addedge(b,a); } if(!k)//如果K为0,计算m^n即可 { printf("%d\n",ksm(m,n)); continue; } lim=min(10000,m);//推导在下面 dfs(1,0); printf("%I64d\n",getsum(1,1)); } return 0; }
lim的推导:
借助样例
Dp[3][1]=dp[3][2]=……=dp[3][10]=1
Dp[2][1]=dp[2][10]=8
Dp[2][2]=dp[2][3]=……=dp[2][9]=7
Dp[1][1]=dp[1][10]=57
Dp[1][2]=dp[1][9]=50
Dp[1][3]=dp[1][4]=……=dp[1][8]=51
同一个点的 dp 值是对称的。 中间有一段的值是相同的。
树的最大深度n-1=99
因为k<=100所以某一端不同的值最多有9900个,简记成10000。
整体思路就是把f[v][i]中的左端实化,右端虚化,在做右端时通过左端对应位置来更新,然后把中间相同部分一并处理。