数据结构与算法部分习题题解
下面这几道题是我做数据结构与算法的习题时有几道比较好的(虽然说比较好但是也就是NOIP普及组水平),做做备份防止忘记~~
Poj 1686 Lazy Math Instructor
题目大意:给定两个表达式,判断两个表达式是否相等。
我们发现如果一步一步搞判断相不相等是非常难做的
但其实有一种非常机智的做法。
给每个字母随机一个数字然后判断两个表达式是否相等即可,重复多几遍就有非常大的概率保证正确。
这种思路的确可以用在挺多看上去非常难但通过枚举取值来解决的问题
Code:
#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef unsigned long long ull;
stack<ull> num;
stack<int> opt;
inline int getopt(char s) {
switch(s) {
case '+':
return 1;
case '-':
return 2;
case '*':
return 3;
}
return 0;
}
ull a[60];
inline ull getnum(char s) {
if ('0'<=s&&s<='9') return s-'0';
if ('a'<=s&&s<='z') return a[s-'a'];
if ('A'<=s&&s<='Z') return a[s-'A'+26];
return 0;
}
inline ull _text(string s) {
int l=s.size();
while (!num.empty()) num.pop();
while (!opt.empty()) opt.pop();
num.push(0);opt.push(1);
for (int i=0;i<l;i++) {
if (s[i]==' '||s[i]=='\t') continue;
if (s[i]=='+'||s[i]=='-'||s[i]=='*') {
int t=getopt(s[i]);
opt.push(t);
continue;
}
if (s[i]=='(') {
num.push(0);opt.push(1);
continue;
}
ull t;
if (s[i]==')') {t=num.top();num.pop();}
else t=getnum(s[i]);
ull u=num.top();
int v=opt.top();
num.pop();opt.pop();
switch (v) {
case 1:
u+=t;
break;
case 2:
u-=t;
break;
case 3:
u*=t;
break;
}
num.push(u);
}
return num.top();
}
string s,t;
int main(){
int __;
cin>>__;
getline(cin,s);
while (__--) {
bool flag=0;
getline(cin,s);
getline(cin,t);
for (int _=1;_<=20;_++) {
for (int i=0;i<26*2;i++) a[i]=rand();
if (_text(s)!=_text(t)) {
printf("NO\n");
flag=1;
break;
}
}
if (!flag) printf("YES\n");
}
return 0;
}
poj 1961:Period
题目大意:一个字符串的前缀是从第一个字符开始的连续若干个字符,例如如"abaab"共有5个前缀,分别是a, ab, aba, abaa, abaab。我们希望知道一个N位字符串S的前缀是否具有循环节。换言之,对于每一个从头开始的长度为 i(i 大于1)的前缀,是否由重复出现的子串A组成,即 AAA...A (A重复出现K次,K 大于 1)。如果存在,请找出最短的循环节对应的K值(也就是这个前缀串的所有可能重复节中,最大的K值)。
由于这道题是在KMP的题目里我们就考虑如何用KMP做这个东西。
我们先把Next数组求出来,我们发现若第x位有解K(K>2)则第\(\frac{x(k-1)}{k}\) 位有解k-1,而该位又恰好是其Next数组的前驱,利用该性质我们就可以递推取得。不过还是要注意一些情况
Code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
#define maxn 1001000
char s[maxn];
int f[maxn],b[maxn];
int main(){
int cnt=1;
scanf("%d",&n);
while (n!=0) {
printf("Test case #%d\n",cnt++);
scanf("%s",s);
f[0]=-1,f[1]=0;
for (int i=2;i<=n;i++) {
int k=f[i-1];
while (k>=0&&s[k]!=s[i-1]) k=f[k];
f[i]=k+1;
}
for (int i=2;i<=n;i++) {
b[i]=0;
if (i%2==0&&f[i]==i/2) b[i]=2;
if (b[f[i]]&&i%(b[f[i]]+1)==0&&f[i]/b[f[i]]==i/(b[f[i]]+1))
b[i]=b[f[i]]+1;
if (b[i]) printf("%d %d\n",i,b[i]);
}
printf("\n");
scanf("%d",&n);
}
return 0;
}
表达式•表达式树•表达式求值
题意:给定一个中缀表达式,求其后缀表达式,再把这个表达式树画出来,最后在把答案算出来。。。
照它说的做,一开始以为超级难,但打完之后才发现不过如此,主要是画图实在是太难画出来了。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
struct node{
char inf;
int pos,dep;
node* ch[2];
node(){
ch[0]=ch[1]=NULL;
}
};
char s[500];
inline int findopt(int l,int r) {
int cnt=0,tmp1=0,tmp2=0;
for (int i=r;i>=l;i--) {
if (s[i]=='(') cnt++;
if (s[i]==')') cnt--;
if (cnt!=0) continue;
if ((!tmp1)&&(s[i]=='+'||s[i]=='-')) tmp1=i;
if ((!tmp2)&&(s[i]=='*'||s[i]=='/')) tmp2=i;
}
if (tmp1) return tmp1;
return tmp2;
}
node* build(int l,int r) {
if (l==r) {
node* t=new node;
t->inf=s[l];
return t;
}
node *pre=new node;
int t=findopt(l,r);
if (t==0) return build(l+1,r-1);
pre->inf=s[t];
pre->ch[0]=build(l,t-1);
pre->ch[1]=build(t+1,r);
return pre;
}
int mxdep;
inline void dfs(node *u){
if (u==NULL) return ;
if (u->ch[0]) {
u->ch[0]->pos=(u->pos<<1)-1;
u->ch[0]->dep=u->dep+1;
}
if (u->ch[1]) {
u->ch[1]->pos=(u->pos<<1);
u->ch[1]->dep=u->dep+1;
}
mxdep=max(mxdep,u->dep);
dfs(u->ch[0]);dfs(u->ch[1]);
printf("%c",u->inf);
}
char st[100][480010];
inline void print(node *u){
if (u==NULL) return ;
int dep=mxdep-u->dep+1,pos=(1<<(dep-1))+(1<<dep)*(u->pos-1);
st[u->dep*2][pos]=u->inf;
if (u->ch[0]) st[u->dep*2+1][pos-1]='/';
if (u->ch[1]) st[u->dep*2+1][pos+1]='\\';
print(u->ch[0]);print(u->ch[1]);
return ;
}
map<char,int> Map;
inline int clac(node *u) {
if (Map.find(u->inf)!=Map.end()) return Map[u->inf];
int x=clac(u->ch[0]),y=clac(u->ch[1]);
if (u->inf=='-') return x-y;
if (u->inf=='+') return x+y;
if (u->inf=='*') return x*y;
if (u->inf=='/') return x/y;
return 0;
}
int main(){
scanf("%s",s+1);
int len = strlen(s+1);
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) {
char tmp[2];int t;
scanf("%s%d",tmp,&t);
Map[tmp[0]]=t;
}
node *root=build(1,len);
root->pos=1;
dfs(root);
printf("\n");
print(root);
int mxlen=0;
for (int i=0;i<=mxdep*2;i++) {
for (int j=1;j<480000;j++)
if (!st[i][j]) st[i][j]=' ';
for (int j=479999;st[i][j]==' ';j--) st[i][j]=0;
printf("%s\n",st[i]+1);
mxlen=max(mxlen,int(strlen(st[i]+1)));
}
printf("%d\n",clac(root));
return 0;
}
poj 2049 Finding Nemo
题目大意 :Nemo 是一个淘气的小孩,一天他独自走进深海,不幸的是,他迷路并找不到回家的路。因此他给他的父亲,Marlin,发了一个信号来求救。
在查看地图后,Marlin发现大海是一个由墙和门构成的迷宫。所有的墙都是平行于X轴或者Y轴的。墙的厚度可以被视为0.所有的门都是在墙上的并且长度为1. Marlin必须通过门来穿过墙。但是穿过一个门是危险的(门附近可能有毒水母),Marlin想穿过尽可能少的门并找到Nemo。我们假设Marlin初始时在(0,0)。给定Nemo的地点,墙和门的分布,请写一个程序来计算Marlin寻找Nemo最少需要穿过多少道门。
就是一道最短路咯,建图比较麻烦,没了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define maxn 41000
#define maxm 180000
#define inf 0x7fffffff
struct edges{
int to,next,dis;
}edge[maxm];
int n,m,id[230][230],Map[maxn][4];
int nxt[maxn],cnt,l;
inline void addedge(int x,int y,int z){
edge[++l]=(edges){y,nxt[x],z};nxt[x]=l;
}
queue<int> q;
bool b[maxn];int dis[maxn];
inline void spfa(int u){
for (int i=1;i<=cnt;i++) dis[i]=inf;
dis[u]=0;
q.push(u);
while (!q.empty()) {
int u=q.front();q.pop();
b[u]=0;
for (int i=nxt[u];i;i=edge[i].next) {
if (dis[edge[i].to]<=dis[u]+edge[i].dis) continue;
dis[edge[i].to]=dis[u]+edge[i].dis;
if (!b[edge[i].to]) {
b[edge[i].to]=1;
q.push(edge[i].to);
}
}
}
}
int w[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int main(){
for (int i=0;i<=200;i++)
for (int j=0;j<=200;j++) id[i][j]=++cnt;
while (scanf("%d%d",&m,&n)!=EOF) {
if (n==-1||m==-1) break;
memset(nxt,0,sizeof(nxt));
l=0;
memset(Map,0,sizeof(Map));
for (int i=1;i<=m;i++) {
int x,y,d,t;
scanf("%d%d%d%d",&x,&y,&d,&t);
for (int j=0;j<t;j++) {
int u=id[x+j*(d^1)][y+j*d],v=id[x+j*(d^1)-d][y+j*d-(d^1)];
Map[u][d*2+1]=Map[v][d*2]=1;
}
}
for (int i=1;i<=n;i++) {
int x,y,d;
scanf("%d%d%d",&x,&y,&d);
int u=id[x][y],v=id[x-d][y-(d^1)];
Map[u][d*2+1]=Map[v][d*2]=-1;
}
for (int i=0;i<=200;i++)
for (int j=0;j<=200;j++)
for (int k=0;k<4;k++) {
int u=i+w[k][0],v=j+w[k][1];
if (u<0||u>200||v<0||v>200) continue;
if (Map[id[i][j]][k]==0) addedge(id[i][j],id[u][v],0);
if (Map[id[i][j]][k]==-1) addedge(id[i][j],id[u][v],1);
}
spfa(id[0][0]);
double v,u;
scanf("%lf%lf",&u,&v);
u=min(u,200.0),v=min(v,200.0);
printf("%d\n",dis[id[int(u)][int(v)]]==inf?-1:dis[id[int(u)][int(v)]]);
}
return 0;
}
POJ 2049 The Peanuts
题目大意:给定一块田地,一只猴子按从大到小的顺序摘花生,求一定时间摘到的花生总数。
这道题只是一道非常普通的模拟,不过因为这题对我有一种特殊的回忆所以我就把它给写了下来(作为我的启蒙课本的一道印象深刻的题目)
Code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef pair<int,int> ii;
#define maxn 55
#define fi first
#define se second
int a[maxn][maxn];
ii id[maxn*maxn];
bool cmp(ii x, ii y) {
return a[x.fi][x.se]>a[y.fi][y.se];
}
int main(){
int _;
scanf("%d",&_);
while (_--) {
int n,m,k,l=0;
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
scanf("%d",&a[i][j]);
id[++l]=ii(i,j);
}
sort(id+1,id+l+1,cmp);
if (id[1].fi+id[1].fi+1>k) {
printf("%d\n",0);
continue;
}
int t=id[1].fi+1,ans=a[id[1].fi][id[1].se];
for (int i=2;i<=l;i++) {
int dis=abs(id[i].fi-id[i-1].fi)+abs(id[i].se-id[i-1].se)+1;
if (t+dis+id[i].fi>k) break;
t+=dis;
ans+=a[id[i].fi][id[i].se];
}
printf("%d\n",ans);
}
return 0;
}
发现它,抓住它
题目大意:有若干个案件,每个案件不是属于团伙A就是属于团伙B,问某两个案件是否属于同一个团伙所为。
依旧是一道印象深刻的题目。。。夏令营的一道题
当年1A了现在要两次真的是变菜了。。。
用并查积储存其案件的相关信息即可。
Code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 101000
int fa[maxn],rela[maxn];
int findfa(int x) {
if (fa[x]==x) return x;
findfa(fa[x]);
rela[x]^=rela[fa[x]];
fa[x]=fa[fa[x]];
return fa[x];
}
int main(){
int _;
scanf("%d",&_);
while (_--) {
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) fa[i]=i,rela[i]=0;
for (int i=1;i<=m;i++) {
int x,y,u,v;
char opt[3];
scanf("%s%d%d",opt,&x,&y);
switch(opt[0]) {
case 'A' :
if (findfa(x)==findfa(y)) {
if (rela[x]==rela[y])
printf("In the same gang.\n");
else printf("In different gangs.\n");
}else printf("Not sure yet.\n");
break;
case 'D':
u=findfa(x),v=findfa(y);
if (u==v) continue;
fa[u]=v;
rela[u]=rela[x]^rela[y]^1;
break;
}
}
}
}
Poj 1830 开关问题
题目大意:给定若干个开关,将某个开关按下会引起其他开关的变化,求到达目标状态的方案。
傻逼题一道。。然而我却傻逼了结果根本想不出来(躺)
一共有两种解法:
解法一:高斯消元
我们将第i个开关是否按下定为\(X_i\) ,如果按下开关i的话第j个开关会变化的话我们就令\(A_{j,i}=1\) 否则\(A_{j,i}=0\) ,则对于开关i,我们有$A_{i,1}X_1 \(^\) A{i,2}X_2\(^\)…\(^\)A{i,n}*A_N=B_i$ 其中^为异或符
我们可以发现该异或方程组一样可以用高斯消元来解决,设自由元数目为M那么答案为\(2^M\)
解法二:双向搜索
虽然解法一能解决这道题,但是看下这道题的tag:高级数据结构,鬼才有用到高级数据结构啦!!!
结果今晚我问了一下黄学长,黄学长听到开关,\(n\leq 29\) 就说到用搜索
我:搜索?搜索不是\(O(2^n)\) 么?
黄学长:双向咯
我:哦。。。。
也就是说,我们先正向bfs一下(也就是按前\(\frac{n}{2}\)个开关,求出所有的可能,再按后\(\frac{n}{2}\)个开关,最后再把这两个结合起来就行啦。
那么怎么求前面的开关的所有的可能呢?
用二进制表示其状态即可。
最后用map或hash映射一下即可。
Code:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 32
int b[maxn][maxn];
int main(){
int _;
scanf("%d",&_);
while (_--) {
int n,cnt=0;
scanf("%d",&n);
memset(b,0,sizeof(b));
for (int i=1;i<=n;i++) scanf("%d",&b[i][n+1]);
for (int i=1;i<=n;i++) {
int x;
scanf("%d",&x);
b[i][n+1]^=x;
}
for (;;) {
int x,y;
scanf("%d%d",&x,&y);
if (x==0&&y==0) break;
b[y][x]=1;
}
for (int i=1;i<=n;i++) b[i][i]=1;
int l=1;
for (int i=1;i<=n+1;i++) {
for (int j=l;j<=n;j++)
if (b[j][i]) {
swap(b[l],b[j]);
break;
}
if (!b[l][i]) {cnt++;continue;}
for (int j=l+1;j<=n;j++) {
if (!b[j][i]) continue;
for (int k=i;k<=n+1;k++) b[j][k]^=b[l][k];
}
l++;
}
int flag=0;
for (int i=1;i<=n;i++) {
if (!b[l-1][i]) continue;
flag=1;
}
if (!flag) printf("Oh,it's impossible~!!\n");
else printf("%d\n",1<<(cnt-1));
}
return 0;
}
Poj 1785 Binary Search Heap Construction
题目大意:要求把一个双关键字的序列按第一关键字排序后按其顺序将该序列转换成以第二关键字为堆的括号序列。
按第一关键字排序直接sort即可,关键是如何将括号序列组成。
首先该序列中的最大元素一定为根,那么其左边为左子树,右边就为右子树,递归即可。接下来的问题就是如何求区间最大值了。
倍增RMQ或线段树都可以。
Code:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<cstdlib>
using namespace std;
typedef pair<int,int> ii;
#define maxn 51000
priority_queue<ii> q;
int adv[maxn];
string label[maxn];
ii f[maxn][30];
int findmx(int l,int r) {
int ans=0;
for (int i=23;i>=0;i--)
if (l+(1<<i)-1<=r) {
if (f[l][i].first>adv[ans]) ans=f[l][i].second;
l+=(1<<i);
}
return ans;
}
void dfs(int u,int l,int r) {
putchar('(');
if (l<u) dfs(findmx(l,u-1),l,u-1);
cout<<label[u]<<'/'<<adv[u];
if (u<r) dfs(findmx(u+1,r),u+1,r);
putchar(')');
}
string st[maxn];
int main(){
for (;;){
int n;
scanf("%d",&n);
if (!n) break;
for (int i=1;i<=n;i++) cin>>st[i];
sort(st+1,st+1+n);
for (int i=1;i<=n;i++) {
string s=st[i];
int pos=s.find('/');
label[i]=s.substr(0,pos);
adv[i]=atoi(s.substr(pos+1,100).data());
f[i][0]=ii(adv[i],i);
}
adv[0]=-0x7fffffff;
for (int i=1;(1<<i)<=n;i++)
for (int j=1;j+(1<<i)-1<=n;j++)
f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
dfs(findmx(1,n),1,n);
puts("");
}
return 0;
}
Poj 3735 Training little cats
题目大意:给定若干只猫,有如下操作,交换坚果,拿坚果,吃坚果,求执行若干次后的答案。
由于操作数不多,但执行次数非常多,所以我们不能直接用模拟解决。
我们考虑将每个猫的坚果数用一个向量表示出来。
那么我们发现每次操作可以用一个矩阵表示出来,那么我们就可以使用矩阵快速幂啦
不过我没有用这种做法。
我们考虑第i轮操作过程,我们可以发现对于每个a,有\(f^i(a)=f^{i-1}(a')+x\) 也就是说这个函数也能快速幂。就能做了。
具体实现看我代码。
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define maxn 11000
typedef long long ll;
int r[maxn],set[maxn],tset[maxn],tr[maxn];
int ansr[maxn],tansr[maxn];
ll ad[maxn],tad[maxn],ansad[maxn],tansad[maxn];
int n,m,k;
int main(){
while (scanf("%d%d%d",&n,&m,&k)!=EOF) {
if (n==0&&m==0&&k==0) break;
for (int i=1;i<=n;i++) r[i]=i,ad[i]=0,set[i]=0;
for (int i=1;i<=k;i++) {
char opt;
scanf("%s",&opt);
if (opt=='g') {
int x;
scanf("%d",&x);
ad[r[x]]++;
}
if (opt=='e') {
int x;
scanf("%d",&x);
ad[r[x]]=0;set[r[x]]=1;
}
if (opt=='s') {
int x,y;
scanf("%d%d",&x,&y);
swap(r[x],r[y]);
}
}
for (int i=1;i<=n;i++) ansr[i]=i,ansad[i]=0;
for (;m;m>>=1) {
if (m&1) {
for (int i=1;i<=n;i++) {
tansr[i]=ansr[r[i]];
tansad[tansr[i]]=ad[r[i]]+(set[r[i]]?0:ansad[tansr[i]]);
}
for (int i=1;i<=n;i++) {
ansr[i]=tansr[i];
ansad[i]=tansad[i];
}
}
for (int i=1;i<=n;i++) {
tr[i]=r[r[i]];
tad[tr[i]]=ad[r[i]]+(set[r[i]]?0:ad[r[r[i]]]);
tset[tr[i]]=set[r[i]]|set[r[r[i]]];
}
for (int i=1;i<=n;i++) r[i]=tr[i],ad[i]=tad[i],set[i]=tset[i];
}
for (int i=1;i<=n;i++) printf("%lld ",ansad[ansr[i]]);
printf("\n");
}
return 0;
}