这场比赛并没有考出自己应有的水平,虽然有偶然因素,但自己的实力问题仍然是成绩不理想的主要条件
这场比赛做的比较好的几点就是对题中的数据范围,内存限制等都看的比较仔细(虽然这场并没有任何用),而且通过这场比赛我终于能手写二分快速幂了……
以下是对几道题的分析和总结:
1、回文图
(palindrome.c/cpp/pas)
【问题描述】
有一片透明玻璃,我们可以在上面涂色。涂色后,你可以对它做两种操作:
1.旋转,顺时针或逆时针旋转90 度;
2.翻转,水平或垂直翻转180 度;
不管进行多少次旋转或翻转,我们看到都是相同的图形,我们把这样的图形称为"回文图"。
下图是操作示例。请注意,图中并不是回文图。
现在给你一块n*n 的方格状透明玻璃和k 种颜色的油漆。请你给每个方格都涂上颜色,
涂完后得到一幅回文图。但是这块玻璃上有m 个方格事先已被涂上了颜色,你不能修改它们。
请问,总共能画出多少种不同的回文图?
【输入格式】
第一行,三个空格间隔的整数n,m,k
接下来m 行,每行两个整数x 和y,表示坐标为(x,y)的格子已被涂上了颜色(0 <= x,y <n)。
【输出格式】
输出仅一行为一个整数,表示方案总数,结果可能很大,请输出Mod 100 000 007 后的结果。
【输入样例1】
3 0 2
【输出样例1】
8
【输入样例2】
4 2 3
1 1
3 1
【输出样例2】
3
【数据范围】
对于50%的数据: 0<n<=500,0<=m<=100,0<k<=100000
对于100%的数据: 0<n<=10000,0<=m<=2000,0<k<=1000000
这题看似比较复杂,但是实际上只要简单分析一下就可以发现正解
首先看到范围就知道搜索肯定是行不通的,因此我们考虑如何去构造一个翻转之后能和自己重合的图形,换句话说,我们要判断怎么样的图形是合法的
对于一个小正方形,它旋转90,180,270度所到达的正方形和翻转后所到达的正方形都可以称作是同一个类型的,因此我们可以考虑构造一个位于原正方形左上角的1/4的小正方形,对于每一个已经涂了色的正方形把它转化到其同类型且位于1/4小正方形之内的正方形
如果在1/4小正方形中有t种类型的正方形,且一共有k种颜色,那么最终答案就是k^t
显然,如果某一种类型中有一个正方形涂上了颜色,那么这种类型就只能涂这一种颜色(这是什么颜色并不影响)
因此答案为k^(t-cnt),cnt为1/4正方形中被涂了颜色的种类数量
#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;
const LL maxn=1e4+5,mod=100000007;
inline void _read(LL &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
LL n,m,k,f[maxn],tg[maxn];
bool Hash[maxn*maxn<<3];
LL MG(LL a,LL b){
LL ans=1;
a%=mod;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans%mod;
}
int main(){
_read(n);_read(m);_read(k);
LL i,j,x,y,t=(n+1)/2;
LL tot=t*(t+1)/2;
for(i=1;i<=t;i++)tg[i]=tg[i-1]+i-1;
for(i=1;i<=m;i++){
_read(x);_read(y);
x++,y++;
if(x>t)x=n+1-x;
if(y>t)y=n+1-y;
if(y>x)swap(x,y);
if(!Hash[tg[x]+y])tot--,Hash[tg[x]+y]=1;
}
cout<<MG(k,tot);
}
2、分礼物
(gift.cpp/c/pas)
【问题描述】
一颗圣诞树挂有很多礼物,每个礼物都挂在树的分叉点或树枝端点上。挂有礼物的点标记为1,没挂礼物的标记为0,现在要把这些礼物连同树枝分给小盆友(当然一个小盆友只能分一个礼物)需要把圣诞树剪成很多小树,且保证一棵小树上有一个礼物。请你计算一下共有多少种不同的剪法方案数。由于答案比较大,只需输出对1000000007取模即可。
【输入格式】
第一行n,表示树共有n 个节点,从0 开始编号。
以下2 到n 行每行一个数,表示编号i-1 的节点的父亲编号
第n+1 行共n 个数,若第i 号节点有礼物,则为1,否则为0.
【输出格式】
一个整数,若无法保证一棵小树上有一个礼物输出0.
【输入样例】
6
0
0
1
1
3
1 0 0 0 1 1
【输出样例】
5
【数据范围】
对于30%的数据:n<=10
对于70%的数据:n<=1000
对于100%的数据:n<=100000
看懂题了就很容易看出来是一个树形递推
f[i][t]表示i号节点的状态,t=0表示i号及其子树没有礼物的方案数,t=1表示有
f[i][0]=f[i][0]*f[j][1]+f[i][0]*f[j][0]
f[i][1]=f[i][1]*f[j][0]+f[i][1]*f[j][1]+f[i][0]*f[j][1]
注意条件判断即可
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define LL long long
using namespace std;
const LL maxn=1e5+5,mod=1000000007;
inline void _read(LL &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
LL f[maxn][2],n,last[maxn],mark[maxn];
struct node{
LL a,b,Next;
node(LL a,LL b,LL Next):a(a),b(b),Next(Next){}
};
vector<node>s;
void insert(LL a,LL b){
s.push_back(node(a,b,last[a]));
last[a]=s.size()-1;
}
LL dfs(LL u){
LL i,v;
f[u][0]=mark[u]^1,f[u][1]=mark[u];
for(i=last[u];i>=0;i=s[i].Next){
v=s[i].b;
dfs(v);
f[u][1]=(f[u][1]*f[v][1]%mod+f[u][1]*f[v][0]%mod+f[u][0]*f[v][1]%mod)%mod;
f[u][0]=(f[u][0]*f[v][1]%mod+f[u][0]*f[v][0])%mod;
}
}
int main(){
memset(last,-1,sizeof(last));
LL i,j,fa;
_read(n);
for(i=1;i<n;i++){
_read(fa);
insert(fa,i);
}
for(i=0;i<n;i++)_read(mark[i]);
dfs(0);
cout<<f[0][1];
}
3、光通讯
(communicate.cpp/c/pas)
【问题描述】
BB 和SS 是一对好盆友,他们制作了两部光学仪器,使用光缆测试通讯。BB 和SS
所在的地方有N 栋楼、M 条双向光缆。每条光缆连接两栋楼,仪器发出的光信号只能沿着
光缆传递,当然通讯需要时间。现在BB 要进行Q 次试验,每次选取两栋楼,并想知道仪
器的光信号在这两栋楼之间传递至少需要多长时间。
说明:N 栋楼通过光缆一定是连通的,光缆连接三类情况:
A:光缆仅有N-1 条。
B:光缆仅有N 条。
C:每条光缆仅在一个环中。
【输入格式】
第一行包含三个用空格隔开的整数,N、M 和Q。
接下来M 行每行三个整数x、y、t,表示楼x 和y 之间有一条传递时间为t 的光缆。
最后Q 行每行两个整数x、y,表示BB 想知道在x 和y 之间通讯最少需要多长时间。
【输出格式】
输出Q 行,每行一个整数,表示BB 每次试验的结果。
【输入样例1】
5 4 2
1 2 1
1 3 1
2 4 1
2 5 1
3 5
2 1
【输出样例1】
3
1
【输入样例2】
5 5 2
1 2 1
2 1 1
1 3 1
2 4 1
2 5 1
3 5
2 1
【输出样例2】
3
1
【输入样例3】
9 10 2
1 2 1
1 4 1
3 4 1
2 3 1
3 7 1
7 8 2
7 9 2
1 5 3
1 6 4
5 6 1
1 9
5 7
【输出样例3】
5
6
【数据规模和约定】
送分数据占10%,2<=N<=1000,N-1<=M<=1200。
A 类数据占30%,M=N-1。
B 类数据占50%,M=N。
C 类数据占10%,M>N。
对于100%的数据,2<=N<=10000,N-1<=M<=12000,Q=10000,1<=x,y<=N,1<=t<32768。
最难的一题,比完回来看着标程做都花了好久……这里就贴一下题解,2011国家队集训的题,太屌了:
本题的图是可以分层的。我们把1号点定为根,然后可以发现,一号点就可以拓展出若干个块,这些块会拓展出一些点,把1号点叫做这些块的原始点,把拓展出一个点的块叫做这个点的原始块,然后这些被拓展的点又可以拓展出一些新的拓展块,重复下去。
在这样的过程中,我们可以发现以下一些性质。
1.一个点只会被拓展一次:因为如果一个点X被拓展了2次,那么说明它被两个不同的块拓展了,设这两个块为G1,G2,那么由于1到G1到X有路径,且1到G2到X有路径,所以G1,G2,1,X应该属于一个块,所以矛盾。
2.一个点只有一个原始块,由性质1易证。
同理一个块也只有一个原始点,又根据性质2,一个点只有一个原始块,那么我们可以把1个点的原始块的原始点叫做这个点的父亲节点,把这个点到父亲节点的最短路计为这个点到其父亲节点的边的权,用这样的一些边来构造一个新的图。显然每个点的父亲节点只有一个且1没有父亲节点,显然N点N-1 条边,这是一棵树。
找到这样一个性质,我们就可以放心大胆的使用这个原来求树上最短路的算法来完成了,特别要注意的是,最后求两点最短路的时候,lca所在的那一层的权值要特殊处理,因为如果是这两点在lca相遇时候是在同一个圈上的时候,权值就为min{TotLenOfCircle-abs(A-B),abs(A-B)},A,B分别是两个点在lca那一层的边权。时间复杂度O(QlogN+NlogN)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=10005,inf=0x3f3f3f3f;
inline void _read(int &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
int n,m,Q,last[maxn],dis[maxn],fa[maxn][15],id[maxn];
int vistime,dfn[maxn],low[maxn],tot_circle,len[maxn];
int d[maxn],maxdep,depth[maxn],q[maxn];
bool vis[maxn];
struct node{
int a,b,c,Next;
node(int a,int b,int c,int Next):a(a),b(b),c(c),Next(Next){}
};
stack<node>ack;
vector<node>s;
void insert(int a,int b,int c){
s.push_back(node(a,b,c,last[a]));
last[a]=s.size()-1;
}
void spfa(){
memset(dis,inf,sizeof(dis));
int l=1,r=2;
q[l]=1,vis[1]=1,dis[1]=0;
while(l!=r){
int x=q[l];
vis[x]=0,l++;
for(int i=last[x];i>=0;i=s[i].Next){
int v=s[i].b;
if(dis[v]>dis[x]+s[i].c){
dis[v]=dis[x]+s[i].c;
if(!vis[v]){
q[r++]=v;
if(r>n)r-=n;
}
}
}
if(l>n)l-=n;
}
}
void getcircle(int f,int x){
tot_circle++;
while(ack.size()&&ack.top().a!=f&&ack.top().b!=x){
int a=ack.top().a,b=ack.top().b,c=ack.top().c;
len[tot_circle]+=c,d[a]=d[b]+c;
if(a!=f)id[a]=tot_circle,fa[a][0]=f;
if(b!=f)id[b]=tot_circle,fa[b][0]=f;
ack.pop();
}
int a=ack.top().a,b=ack.top().b,c=ack.top().c;
len[tot_circle]+=c,d[a]+=d[b]+c,fa[b][0]=a;
ack.pop();
}
void tajan(int x,int f){
dfn[x]=low[x]=++vistime;
int i,j;
for(i=last[x];i>=0;i=s[i].Next){
int v=s[i].b;
if(!dfn[v]){
ack.push(s[i]);
tajan(v,x);
low[x]=min(low[x],low[v]);
if(dfn[x]<=low[v])getcircle(x,v);
}
else if(v!=f&&dfn[v]<low[x]){
low[x]=dfn[v];
ack.push(s[i]);
}
}
}
void dfs(int x,int dep){
depth[x]=dep;
for(int i=last[x];i>=0;i=s[i].Next)
if(!depth[s[i].b])dfs(s[i].b,dep+1);
}
int LCA(int x,int y,int &sx,int &sy){
int temp=0;
if(depth[x]<depth[y])swap(x,y);
temp=dis[x]+dis[y];
sx=sy=y;
int i;
for(i=maxdep;i>=0;i--)
if(depth[fa[x][i]]>=depth[y])x=fa[x][i];
if(x==y)return temp-2*dis[y];
for(i=maxdep;i>=0;i--)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
sx=x,sy=y;
return temp-2*dis[fa[x][0]];
}
int main(){
memset(last,-1,sizeof(last));
_read(n);_read(m);_read(Q);
int i,j,x,y,z,sx,sy;
for(i=1;i<=m;i++){
_read(x);_read(y);_read(z);
insert(x,y,z);insert(y,x,z);
}
spfa();
tajan(1,0);
maxdep=(int)(log(n)/log(2));
for(j=1;j<=maxdep;j++)
for(i=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1];
s.clear();memset(last,-1,sizeof(last));
for(i=2;i<=n;i++)insert(fa[i][0],i,0);
dfs(1,1);
for(i=1;i<=Q;i++){
_read(x);_read(y);
int ans=LCA(x,y,sx,sy);
if(id[sx]!=0&&id[sx]==id[sy]){
ans=dis[x]+dis[y]-dis[sx]-dis[sy];
int t1=abs(d[sx]-d[sy]);
int t2=len[id[sx]]-t1;
ans+=min(t1,t2);
}
printf("%d\n",ans);
}
}
总结一下:
自己还是对题目的分析不够,代码快写完了才发现想法有问题
下次做题就应该先简单证明一下结论,多找找有没有反例,不然太亏了