题目链接:https://www.patest.cn/contests/gplt
数组索引应用
L2_002 链表去重
题目要求补充: 若没有需要删除的节点,则不输出删除节点。
思路:通过自身地址和next遍历链表,对每一个进行节点的值进行判断保留与否,将其地址放入res数组或者del数组
tip:直接利用数组的索引作地址
复杂度:O(n)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
const int maxv = 10005;
int llist[2][maxn],res[maxn],del[maxn];
int ab[maxv];
int main(){
// freopen("002.txt","r",stdin);
int st,n;
scanf("%d%d",&st,&n);
for(int i = 0;i < n;i++){
int add;
scanf("%d",&add);
scanf("%d%d",&llist[0][add],&llist[1][add]); //list[0]存值,list[1]存下一个节点
}
/*
*通过自身地址和next遍历链表,对每一个进行节点的值进行判断
*/
int inr = 0, ind = 0;
int next = st;
while(next != -1){
int val = abs(llist[0][next]);
if(ab[val]) del[ind++] = next;
else {ab[val] = 1; res[inr++] = next; }
next = llist[1][next];
}
del[ind] = -1;
res[inr] = -1;
for(int i = 0;i < inr-1; i++) printf("%05d %d %05d\n",res[i],llist[0][res[i]],res[i+1]);
printf("%05d %d %d\n",res[inr-1],llist[0][res[inr-1]],res[inr]);
if(ind>0){ //当有删除节点时才输出
for(int i = 0;i < ind-1; i++) printf("%05d %d %05d\n",del[i],llist[0][del[i]],del[i+1]);
printf("%05d %d %d\n",del[ind-1],llist[0][del[ind-1]],del[ind]);
}
}
L2_018 多项式A除以B
思路:模拟多项式除法的步骤。
rs是商多项式,rm是余多项式,mid是运算时的中间多项式。伪代码:
rm = A;
while(当rm的最高次大于等于除式B的最高次时){
rs的第i项 cur = rm最高次系数 / B最高次系数;
mid 的各系数 = cur * B的各系数;
rm = rm - mid;
}
在数据结构上,用数组存储多项式,索引为指数,对应的值为该项的系数。
如rs[3] = -1 表示 -x^3
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000;
struct mul{
int high;
double C[maxn];
}A,B,rs,rm;
void read(int n ,mul& mm){ //读数
for(int i=0;i<n;i++){
int s1,s2;
scanf("%d%d",&s1,&s2);
mm.C[s1] =(double) s2;
if(i==0) mm.high = s1;
}
}
double ff(double cc){ //对系数进行四舍五入计算
int bb = (int)(abs(cc)*100);
int aa = bb % 10;
bb = aa>=5? bb+10-aa: bb - aa;
return cc >= 0? bb/100.0 : -1*bb/100.0;
}
int main(){
// freopen("018.txt","r",stdin);
int n;
scanf("%d",&n);
read(n,A);
scanf("%d",&n);
read(n,B);
rs.high = A.high - B.high; //结果的最高位
int b = rs.high;
rm = A; //rm是每次的被除式
for(int i=A.high;i>=B.high;i--){ //i为当前被除式的最高项
double sh = rm.C[i] / B.C[B.high]; //当前的商
rs.C[b] = sh;
mul mid; //中间多项式
mid.high = i;
for(int j = B.high;j >= 0;j--){
mid.C[b + j] = B.C[j] * sh;
}
for(int k = rm.high;k >= 0;k--){
rm.C[k] -=mid.C[k];
}
rm.high = i-1;
b--;
}
int cnt1=0,cnt2=0; //对商和余数的系数进行处理,并计数
for(int i = rs.high; i>=0;i--) {
double cc = rs.C[i];
rs.C[i] = ff(cc);
if(rs.C[i]!=0.0) cnt1++;
}
for(int i = rm.high; i>=0;i--) {
double cc = rm.C[i];
rm.C[i] = ff(cc);
if(rm.C[i]!=0.0) cnt2++;
}
//输出
if(cnt1==0) printf("0 0 0.0");
else {
printf("%d",cnt1);
for(int i = rs.high; i>=0;i--){
if(rs.C[i]!=0.0) printf(" %d %.1f",i,rs.C[i]);
}
}
printf("\n");
if(cnt2==0 ) printf("0 0 0.0");
else{
printf("%d",cnt2);
for(int i = rm.high; i>=0;i--){
if(rm.C[i]!=0.0) printf(" %d %.1f",i,rm.C[i]);
}
}
}
树
L2_004 这是二叉搜索树吗
思路一:假定给定序列是所给二叉树的前序遍历结果A,将序列从小到大排序可得“二叉搜索树”的中序遍历结果B。如果该树是二叉搜索树,则根据A B 可以顺利建树; 若不能,将序列从大到小排序得到“二叉搜索树的镜像树”的中序遍历结果C。同理若其镜像树为二叉搜索树,则根据A C可以顺利建树。
复杂度:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<functional>
using namespace std;
const int maxn=1005;
int pre[maxn],in[maxn],lef[maxn],rig[maxn];
bool isBST=true;
int Tree(int L1,int R1,int L2,int R2){ //依据先序和中序建树
if(L1>R1) return -1;
int root=pre[L1];
int lenl,lenr,i;
for(i=L2;i<=R2;i++) if(root==in[i]) {lenl=i-L2;break;}
if(i>R2) {
isBST=false;
return -1;
}
lenr=R2-L2-lenl;
lef[L1]=Tree(L1+1,L1+lenl,L2,L2+lenl-1);
rig[L1]=Tree(R1-lenr+1,R1,R2-lenr+1,R2);
return L1;
}
void postTra(int root){
if(root==-1) return;
postTra(lef[root]);
postTra(rig[root]);
if(root==0)printf("%d",pre[root]);
else printf("%d ",pre[root]);
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&pre[i]);
in[i]=pre[i];
}
sort(in,in+n); //判断其本身是不是BST
Tree(0,n-1,0,n-1);
if(isBST) {
printf("YES\n");
postTra(0);
}
/*
*下面,由于Tree中的算法缺陷,当有重复的数的时候,会出错。就是样例那种情况过不了
*/
else{ //判断其是不是 BST的镜像
isBST=true;
sort(in,in+n,greater<int>());
memset(lef,0,sizeof(lef));
memset(rig,0,sizeof(rig));
Tree(0,n-1,0,n-1);
if(isBST){
printf("YES\n");
postTra(0);
}
else printf("NO");
}
}
思路二:直接利用前序遍历结果建树。假设其本身(其镜像)是二叉搜索树,找到第一个大于等于(小于)根的节点A,则得到左子树和右子树;且有右子树的所有点大于等于(小于)根,这个可以用来判断该前序遍历结果究竟是不是二叉搜索树:若右子树存在某点小于(或大于等于)根,则不是。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int pre[maxn];
bool isB;
int n;
struct node{
int val;
node *lef,*rig;
};
node* build(int l, int r,char c){
if(l > r) return NULL;
node *rt = new node();
if(l == r){
rt->val = pre[l];
rt->lef = NULL;
rt->rig = NULL;
return rt;
}
rt->val = pre[l];
int p = l + 1;
if(c == '>'){ //本身是二叉搜索树
for(;p <= r; p++) if(pre[p] >= pre[l]) break;
for(int j = p; j < n; j++) if(pre[j] < pre[l]){ isB = false; return NULL;}
}
else if(c == '<'){ //其镜像是二叉搜索树
for(;p <= r; p++) if(pre[p] < pre[l]) break;
for(int j = p; j < n; j++) if(pre[j] >= pre[l]){ isB = false; return NULL;}
}
rt->lef = build(l + 1,p - 1,c);
rt->rig = build(p,r,c);
return rt;
}
void print(node* rt,int i){
if(rt == NULL) return;
print(rt->lef, i + 1);
print(rt->rig, i + 1);
if(i == 0) printf("%d",rt->val); //最后打印这个
else printf("%d ",rt->val);
}
int main(){
scanf("%d",&n);
for(int i = 0; i < n; i++) scanf("%d",&pre[i]);
isB = true;
node *root = build(0,n - 1, '>');
if(isB) {printf("YES\n");print(root,0);} //判本身
else{ //判镜像
isB = true;
root = build(0,n - 1, '<');
if(isB){printf("YES\n");print(root,0);}
else printf("NO");
}
}
L2_006 树的遍历
思路:用lef[i] , rig[i]存储编号为i的节点的左右子节点的编号。
#include<cstdio>
#include<queue>
using namespace std;
#define rep(i,n) for(int i=0;i<n;i++)
const int maxn=35;
int lef[maxn],rig[maxn],in[maxn],post[maxn];
int n;
int Tree(int L1,int R1,int L2,int R2){
if(L1>R1)return -1;
int root=post[R1];
int lenl,lenr;
for(int i=L2;i<=R2;i++) if(in[i]==root){lenl=i-L2; break;}
lenr=R2-L2-lenl;
lef[R1]=Tree(L1,L1+lenl-1,L2,L2+lenl-1);
rig[R1]=Tree(R1-lenr,R1-1,R2-lenr+1,R2);
return R1;
}
int main(){
// freopen("data006.txt","r",stdin);
scanf("%d",&n);
rep(i,n) scanf("%d",&post[i]);
rep(i,n) scanf("%d",&in[i]);
Tree(0,n-1,0,n-1);
queue<int>q;
q.push(post[n-1]);
while(!q.empty()){
int t=q.front();
q.pop();
int k;
rep(i,n)if(post[i]==t){k=i;break;}
if(lef[k]!=-1) q.push(post[lef[k]]);
if(rig[k]!=-1) q.push(post[rig[k]]);
if(t==post[n-1]) printf("%d",t);
else printf(" %d",t);
}
}
指针版:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 35;
int post[maxn],in[maxn];
struct Node{
int val;
Node *lef,*rig;
};
Node *root;
Node* build(int l1,int r1,int l2,int r2){
if(l1 > r1) return NULL;
Node *t = new Node();
if(l1 == r1){
t->val = post[r1];
t->lef = NULL;
t->rig = NULL;
return t;
}
int k = post[r1],lenl,lenr;
for(lenl = l2; lenl <= r2;lenl++) if(in[lenl] == k) break;
lenl -= l2; lenr = r2 - l2 - lenl;
t->val = k;
t->lef = build(l1,l1 + lenl - 1,l2,l2 + lenl - 1);
t->rig = build(r1 - lenr,r1 - 1,r2 - lenr + 1,r2);
return t;
}
void BFS(){
queue<Node*>que;
que.push(root);
while(!que.empty()){
Node* cur = que.front();que.pop();
if(cur == root) printf("%d",cur->val);
else printf(" %d",cur->val);
if(cur->lef!=NULL) que.push(cur->lef);
if(cur->rig!=NULL) que.push(cur->rig);
}
}
int main(){
// freopen("data006.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d",&post[i]);
for(int i = 0;i < n;i++) scanf("%d",&in[i]);
root = NULL;
root = build(0,n-1,0,n-1);
BFS();
return 0;
}
L2_011 玩转二叉树
思路:BFS时先让右子树进入队列以实现翻转
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=35;
#define rep(i,n) for(int i=0;i<n;i++)
int in[maxn],pre[maxn],lef[maxn],rig[maxn];
int Tree(int L1,int R1,int L2,int R2){
if(L2>R2) return -1;
int root=pre[L2];
int lenl,lenr;
for(int i=L1;i<=R1;i++) if(in[i]==root){lenl=i-L1;break;}
lenr=R1-L1-lenl;
lef[L2]=Tree(L1,L1+lenl-1,L2+1,L2+lenl);
rig[L2]=Tree(R1-lenr+1,R1,R2-lenr+1,R2);
return L2;
}
int main(){
// freopen("data011.txt","r",stdin);
int n;
scanf("%d",&n);
rep(i,n) scanf("%d",&in[i]);
rep(i,n) scanf("%d",&pre[i]);
Tree(0,n-1,0,n-1);
queue<int>q;
q.push(pre[0]);
while(!q.empty()){
int cur=q.front();
q.pop();
int tt;
rep(i,n) if(pre[i]==cur){tt=i;break;}
if(rig[tt]!=-1) q.push(pre[rig[tt]]); //先让右子树进入队列
if(lef[tt]!=-1) q.push(pre[lef[tt]]);
if(cur==pre[0]) printf("%d",cur);
else printf(" %d",cur);
}
}
指针版:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 35;
int in[maxn], pre[maxn];
struct node{
int val;
node *lef, *rig;
};
node* BT(int l1,int r1,int l2,int r2){ //建树
if(l1 > r1)return NULL;
node *rt = new node();
if(l1 == r1){
rt->val = pre[l1];
rt->lef = NULL;
rt->rig = NULL;
return rt;
}
rt->val = pre[l1];
int lenl = l2, lenr;
for(;lenl <= r2; lenl++)
if(in[lenl] == pre[l1]) break;
lenl = lenl - l2;
lenr = r2 - l2 -lenl;
rt->lef = BT(l1 + 1, l1 + lenl, l2, l2 + lenl - 1);
rt->rig = BT(r1 - lenr + 1, r1, r2 - lenr + 1,r2);
return rt;
}
void sswap(node* rt){ //镜面反转
if(rt == NULL) return;
if(rt->lef == NULL && rt->rig == NULL) return;
sswap(rt->lef);
sswap(rt->rig);
node *temp = rt->lef;
rt->lef = rt->rig;
rt->rig = temp;
}
int main(){
// freopen("011.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i = 0; i < n; i++) scanf("%d",&in[i]);
for(int i = 0; i < n; i++) scanf("%d",&pre[i]);
node* root = new node();
root = BT(0,n - 1,0,n - 1);
sswap(root);
queue<node*>que; //层序遍历
que.push(root);
while(!que.empty()){
node* temp = que.front();
que.pop();
if(temp->val == root->val) printf("%d",temp->val);
else printf(" %d",temp->val);
if(temp->lef != NULL) que.push(temp->lef);
if(temp->rig != NULL) que.push(temp->rig);
}
}
set应用
L2_005 集合相似度
思路:Nc、Nt分别可以看做是两个集合的交集和并集的元素个数。
tip:可以使用set_union和set_intersection 得到元素个数, 但是这样会超时。因而使用 card(A+B) = card(A) + card(B) - card(A*B) 得到并集个数
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
const int maxn=55;
const int maxk=2005;
set<int>sets[maxn];
set<int> bing,jiao;
void getUnion(int i,int j){ //对集合set进行并交操作,其实并操作可以省去
set_union(sets[i].begin(),sets[i].end(),sets[j].begin(),sets[j].end(),inserter(bing,bing.begin()));
return;
}
void getIntersection(int i,int j){
set_intersection(sets[i].begin(),sets[i].end(),sets[j].begin(),sets[j].end(),inserter(jiao,jiao.begin()));
return;
}
int main(){
// freopen("data005.txt","r",stdin);
int n,m,k;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&m);
for(int j=0;j<m;j++) {
int nm;
scanf("%d",&nm);
sets[i].insert(nm);
}
}
scanf("%d",&k);
while(k){
int s1,s2;
scanf("%d%d",&s1,&s2);
int cnt1,cnt2,cntb,cntj;
cnt1=sets[s1].size();
cnt2=sets[s2].size();
bing.clear();
jiao.clear();
// getUnion(s1,s2);
// cntb=bing.size(); //如果直接使用union后获取size的话,会超时。
getIntersection(s1,s2);
cntj=jiao.size();
cntb=cnt1+cnt2-cntj;
double rate=(double)cntj/cntb*100;
printf("%.2lf%\n",rate);
k--;
}
}
L2_016 愿天下有情人都是失散多年的兄妹
思路:将两个异性五代之内的亲人分别放在数组里,通过交运算判断是否为兄妹。
其中,用BFS获得五代之内的亲人,为控制“五代”,使用了5个queue。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
struct ps{
int f,m;
char s[2];
}p[maxn];
set<int> fam[maxn];
set<int> jiao;
int vis[maxn]; //标记已获取过某人i的亲属集合,避免重复获取
queue<int>que[5];
void getFam(int lv){ //BFS获得亲属
for(int i = 0; i < 5; i++)
while(!que[i].empty()) que[i].pop();
que[0].push(lv);
for(int i = 0;i < 5; i++){
while(!que[i].empty()){
int cur = que[i].front();
que[i].pop();
fam[lv].insert(cur);
if(i != 4){
if(p[cur].f!=-1) que[i+1].push(p[cur].f);
if(p[cur].m!=-1) que[i+1].push(p[cur].m);
}
}
}
}
void getIntersection(int lv1,int lv2){
jiao.clear();
set_intersection(fam[lv1].begin(),fam[lv1].end(),fam[lv2].begin(),fam[lv2].end(),inserter(jiao,jiao.begin()));
}
int main(){
//freopen("data016.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++){
int id, fa, mo;
scanf("%s %d %d",p[id].s,&fa,&mo);
p[id].f = fa;
p[id].m = mo; //对其父母进行处理
if(fa != -1) {p[fa].f = -1; p[fa].m = -1;p[fa].s[0]= 'M';}
if(mo != -1) {p[mo].f = -1 ;p[mo].m = -1;p[mo].s[0] ='F';}
}
int m;
scanf("%d",&m);
for(int i = 0;i < m;i++){
int lv1,lv2;
scanf("%d%d",&lv1,&lv2);
if(p[lv1].s[0]==p[lv2].s[0]){printf("Never Mind\n");continue;}
if(!vis[lv1]){
vis[lv1] = 1;
getFam(lv1);
}
if(!vis[lv2]){
vis[lv2] = 1;
getFam(lv2);
}
getIntersection(lv1,lv2);
if(jiao.size()==0) printf("Yes\n"); //是否有相同亲属
else printf("No\n");
}
}
L2_019 悄悄关注
思路:用set存储非悄悄关注的名字,找到点赞数在平均値之上的名字,判断是否在set中。
tip:一开始我用集合的并运算进行判断,超时; 后改用set自己的方法就好了
#include <bits/stdc++.h>
using namespace std;
set<string> gz,temp,jiao;
map<string,int> dz;
//void getUnion(){
// set_union(gz.begin(),gz.end(),temp.begin(),temp.end(),inserter(gz,gz.begin()));
//}
int main(){
// freopen("002.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
string name;
cin>>name;
gz.insert(name);
}
int m;
scanf("%d",&m);
int sum=0;
for(int i=0;i<m;i++){
string nm;
int cs;
cin>>nm>>cs;
dz[nm]=cs;
sum+=cs;
}
double avg=(double)sum/m;
bool flag=true;
map<string,int>::const_iterator it;
for(it=dz.begin();it!=dz.end();++it)
if(it->second>avg && gz.find(it->first) == gz.end()){ //将集合运算改成这一句话,就不会超时
// temp.clear();
// temp.insert(it->first);
// int cntq=gz.size();
// getUnion();
// int cnth=gz.size();
cout<<it->first<<endl;flag=false;
}
if(flag) cout<<"Bing Mei You"<<endl;
}
并查集
L2_007 家庭财产
思路一:利用集合来存储每个家庭的成员。
由于各个成员(包括每条信息自身和在信息中出现的)间的亲属关系比较难找,
所以对于每一个人,依据其编号、父母和子女的编号,对现有的所有family进行遍历,看是否能找到其亲属
若在现有的中找到了,则将该条信息中的所有人插入相应的set中;若没有,则新建一个set存储
由于个人的信息给出的顺序会影响到set的建立,即是一家人的两个人以及亲属,由于两个人之间的联系人没有出现,会暂时存于两个set中
所以,这题不能在找到一个set之后,就break,而是对于每一个编号都要遍历所有的set,因为他的亲戚set不只一个 。一个人的信息中的所有编号都要去找其所有的亲属set, 因为所有编号都可能成为纽带
在set建立完以后,就是set的合并了。用的set的交集和并集
复杂度:O(n^3)
#include <cstdio>
#include <set>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=1005;
const int maxk=5;
struct person{ //给出的个人信息
int ID[3+maxk]; //i=0:ID, 1:fID, 2:mID, 3~2+k:kids's ID
int k,hc,ar;
}p[maxn];
struct family{ //家庭信息
int minID,mcnt;
double avrh,avra;
}res[maxn]; //最终的结果
set<int>fam[maxn];
set<int>jiao;
int visited[maxn]; //标记
void getUnion(int i,int j){
set_union(fam[i].begin(),fam[i].end(),fam[j].begin(),fam[j].end(),inserter(fam[i],fam[i].begin()));
}
void getIntersection(int i,int j){
set_intersection(fam[i].begin(),fam[i].end(),fam[j].begin(),fam[j].end(),inserter(jiao,jiao.begin()));
}
bool cmp(const family& f1,const family& f2){ //对最后的结果进行排序 (结构体的排序)
return f1.avra>f2.avra||(f1.avra==f2.avra&&f1.minID<f2.minID);
}
int main(){
// freopen("data007.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){ //输入
scanf("%d%d%d%d",&p[i].ID[0],&p[i].ID[1],&p[i].ID[2],&p[i].k);
for(int j=3;j<3+p[i].k;j++)scanf("%d",&p[i].ID[j]);
scanf("%d%d",&p[i].hc,&p[i].ar);
}
int hcnt=0; //记录已创建的家庭数目(有的家庭可能是一个)
for(int i=0;i<n;i++){ //每个人
memset(visited,0,sizeof(visited)); //是为了防止重复插入到一个家庭中 ,只要一个人中有一个编号与家庭A有关系插入后,此人其他编号就不用他们的for循环中插入了
bool flag=true; //用于记录是否存在与此人有联系的家庭
for(int j=0;j<3+p[i].k;j++) { //每个人中的每个编号
for(int x=0;x<hcnt;x++){
if(visited[x]==0){
set<int>::iterator it; //看家庭中有没有和当前编号一样的,如有,则说明是一家人
for(it=fam[x].begin();it!=fam[x].end();++it)
if(*it==p[i].ID[j]){ //插入
flag=false;
visited[x]=1;
for(int y=0;y<3+p[i].k;y++){
if(p[i].ID[y]!=-1) fam[x].insert(p[i].ID[y]);
}
}
}
}
}
if(flag){ //新建家庭
for(int y=0;y<3+p[i].k;y++){
if(p[i].ID[y]!=-1) fam[hcnt].insert(p[i].ID[y]);
}
hcnt++;
}
}
for(int i=0;i<hcnt;i++) //合并实质上是一个家庭分散在各个set中的成员
for(int j=i+1;j<hcnt;j++){
if(!fam[i].empty()&&!fam[j].empty())
getIntersection(i,j);
if(jiao.size()!=0){getUnion(i,j);fam[j].clear();}
}
int ans=0;
for(int i=0;i<hcnt;i++){ //生成家庭信息,即遍历某一个set,统计
if(!fam[i].empty()){
res[ans].minID=*fam[i].begin();
res[ans].mcnt=fam[i].size();
double sumh=0,suma=0;
set<int>::iterator it;
for(it=fam[i].begin();it!=fam[i].end();++it){
int j;
for(j=0;j<n;j++) if(p[j].ID[0]==*it)break;
sumh+=p[j].hc;
suma+=p[j].ar;
}
res[ans].avrh=(double)sumh/fam[i].size();
res[ans].avra=(double)suma/fam[i].size();
ans++;
}
}
sort(res,res+ans,cmp);
printf("%d\n",ans); //打印
for(int i=0;i<ans;i++){
printf("%04d %d %.3lf %.3lf\n",res[i].minID,res[i].mcnt,res[i].avrh,res[i].avra);
}
}
思路二:并查集 ,将一个家庭中编号小的作为根。
处理上,先完整存储输入,再进行并查操作分离。目的是为了在并操作的同时更新家庭信息。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int maxb = 10000;
//int fa[maxn],exs[maxn]; 段错误
int fa[maxb],exs[maxb],vis[maxb]; //exs: 标记编号为i的人是否是题中给出的有房子的人,用于并操作入口; vis: 标记编号为i的人是否存在
struct person{ // 题中给出的有房子的人
int ID, num;
vector<int>mem; // 这人的亲属
double ps,par;
person(int _num = 1, double _ps = 0.0,double _par = 0.0):num(_num),ps(_ps),par(_par){}
}p[maxb]; //段错误:不是p[maxn]
struct family{
int minId, fnum;
double as, aar;
family(int _fnum = 0,double _as = 0.0,double _aar = 0.0):fnum(_fnum),as(_as),aar(_aar){};
}fam[maxb]; //段错误:不是p[maxn]
void setP(int id1 ,int id2){ //进行合并操作的时候,同时进行更新,使根始终保持着整个家庭的信息
fa[id2] = id1;
p[id1].num += p[id2].num;
p[id1].ps += p[id2].ps;
p[id1].par += p[id2].par;
}
int Find(int id){
return fa[id] == id? id : fa[id] = Find(fa[id]);
}
void Union(int id1, int id2){
int fa1 = Find(id1);
int fa2 = Find(id2);
if(fa1 != fa2){ //要排除这种情况,否则setp会错(自己加自己)
if(fa1 > fa2) setP(fa2,fa1);
else setP(fa1,fa2);
}
}
bool cmp(const family& a, const family& b) {
return a.aar > b.aar || (a.aar == b.aar && a.minId < b.minId);
}
int main(){
// freopen("data007.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i = 0;i < maxb;i++) fa[i] = i;
for(int i = 0; i < n; i++){ //输入
int sid, fid, mid, k;
scanf("%d%d%d%d",&sid,&fid,&mid,&k);
exs[sid] = 1;
vis[sid] = 1;
p[sid].ID = sid;
if(fid != -1) {p[sid].mem.push_back(fid);vis[fid] = 1;}
if(mid != -1) {p[sid].mem.push_back(mid); vis[mid] = 1;}
int kid;
for(int j = 0;j < k;j++){
scanf("%d",&kid);
p[sid].mem.push_back(kid);
vis[kid] = 1;
}
scanf("%lf%lf",&p[sid].ps,&p[sid].par);
}
for(int i = 0; i < maxb;i++) // U/F
if(exs[i]) //只对有房子的进行遍历
for(int j = 0;j < p[i].mem.size();j++)
Union(i,p[i].mem[j]);
int cnt = 0;
for(int i = 0; i < maxb;i++)
if(vis[i]) //每个出现的人都有机会成为根,故均进行考察
if(Find(i) == i){
fam[cnt].minId = i;
fam[cnt].fnum = p[i].num;
fam[cnt].as = p[i].ps / p[i].num;
fam[cnt].aar = p[i].par /p[i].num;
cnt++;
}
sort(fam,fam+n,cmp);
printf("%d\n",cnt);
for(int i = 0;i < cnt;i++){
printf("%04d %d %.3lf %.3lf\n", fam[i].minId,fam[i].fnum,fam[i].as,fam[i].aar);
}
}
L2_010 排座位
思路一:对于不是直接朋友的两人,通过求可达矩阵判断两人直接是否有公共朋友。(一开始不理解“朋友的朋友也是朋友。但敌人的敌人并不一定就是朋友,朋友的敌人也不一定是敌人”的含义,只是处理了矩阵和矩阵逆的乘积)
思路二:并查集。用矩阵存储题面所给的直接关系,用并查集划分朋友。矩阵可能的值为-1,0,1。为“1”时可直接确定输出,0和-1 还要利用并查集确定是否有公共朋友后确定输出。
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=105;
int fe[maxn][maxn];
int fat[maxn];
int find(int nd){
int f = fat[nd];
return nd== f? nd:fat[nd]=find(f);
}
void Union(int nd1,int nd2){
if(fe[nd1][nd2] == 1){
int f1 = find(nd1);
int f2 = find(nd2);
if(f1!=f2) f1>=f2? fat[f1]=f2 : fat[f2]=f1;
}
}
bool hcf(int nd1,int nd2){ //不是直接朋友关系的两个人(矩阵中为0 或 -1)是否有共同的朋友
return find(nd1)==find(nd2);
}
int main(){
// freopen("010.txt","r",stdin);
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++){ //矩阵存储直接关系
int x,y,re;
scanf("%d%d%d",&x,&y,&re);
fe[x][y]=fe[y][x]=re;
}
for(int i=1;i<=n;i++) fat[i]=i;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)
Union(i,j);
}
for(int i=0;i<k;i++){
int x,y;
scanf("%d%d",&x,&y);
if(fe[x][y]==1) printf("No problem\n");
else if(fe[x][y]==0){
if(hcf(x,y)) printf("No problem\n");
else printf("OK\n");
}
else{
if(hcf(x,y)) printf("OK but...\n");
else printf("No way\n");
}
}
}
L2_013 红色警报
思路:并查集。
当一个城市被删掉时,可能出现三种情况:
1)其他城市的连通关系改变,此时连通支增多;
2)该城市原本便不与其他任何城市相连,删去后不会改变其他城市的连通关系,此时连通支减少;
3)该城市虽然与其他任何城市相连,但其删去后不会改变其他城市的连通关系,比如该城市是一个悬挂点,此时连通支不变;
因此本题可转化为研究删除某节点之后,图的连通支变化。
删除前后,用并查集生成连通支,并确定数量,如果连通支增多,则发出红色警报。
错误方法:
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=505;
const int maxm=5005;
int ff[2][maxn],ct[maxn][maxn],del[maxn],fat[maxn]; //ff[2]交替使用存储并查集中的根节点
int find(int i,int *f){
return f[i]==i? i : f[i]=find(f[i],f);
}
void Union(int i,int j,int *f){
int fa1 = find(i,f);
int fa2 = find(j,f);
if(fa1 == fa2) return;
fa1>=fa2?f[i]=fa2:f[j]=fa1;
}
int count(int *f,int nn){ //数连通支数量
memset(fat,0,sizeof(fat));
int cnt=0;
for(int i=0;i<nn;i++){
if(f[i]!=-1){
if(fat[f[i]]==0){
fat[f[i]]=1;
cnt++;
}
}
}
return cnt;
}
int main(){
// freopen("data013.txt","r",stdin);
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++){
int v1,v2;
scanf("%d%d",&v1,&v2);
ct[v1][v2]=ct[v2][v1]=1;
}
for(int j=0;j<n;j++) ff[0][j]=j; //最开始的并查集
for(int x=0;x<n;x++)
for(int y=x+1;y<n;y++){
if(ct[x][y]==1) Union(x,y,ff[0]);
}
int k;
scanf("%d",&k);
for(int i=0;i<k;i++){
int lc;
scanf("%d",&lc);
del[lc]=1; //删除前
for(int z=lc+1;z<n;z++)if(ct[lc][z]) ct[lc][z]=0;
for(int z=0;z<lc;z++)if(ct[z][lc]) ct[z][lc]=0;
int in=(i+1)%2; //删除后
for(int j=0;j<n;j++) if(del[j]!=1)ff[in][j]=j;else ff[in][j]=-1;
for(int x=0;x<n;x++)
for(int y=x+1;y<n;y++){
if(ct[x][y]==1) Union(x,y,ff[in]);
}
bool isChanged=false;
for(int x=0;x<n;x++){
for(int y=x+1;y<n;y++){
if(x!=lc&&y!=lc)
{ int cnt1=count(ff[in],n);
int cnt2=count(ff[(in+1)%2],n);
if(cnt1>cnt2){
isChanged=true;
break;
}
}
}
if(isChanged) break;
}
if(isChanged) printf("Red Alert: City %d is lost!\n",lc);
else printf("City %d is lost.\n",lc);
}
if(k==n) printf("Game Over.");
return 0;
}
正确方法:
从反向考虑。 先假设给出城市全部被删去,有一个初始的图。然后从最后一个城市开始,添加城市,每填一个城市,更新(不是重建)并查集,更新连通块的数目。
ans 用于离线输出。
#include <bits/stdc++.h>
using namespace std;
int const maxn = 505;
int fa[maxn],del[maxn],vis[maxn],ans[maxn]; //del 失守城市序列,vis 标记城市是否处于删除状态 ans添加城市后的连通块数目
int pa[maxn][maxn];
int cnt;
int Find(int i){
return fa[i] == i? i: fa[i] = Find(fa[i]);
}
void Union(int i, int j){
int fa1 = Find(i);
int fa2 = Find(j);
if(fa1 != fa2){
cnt--; //合并时连通块数目减少
// fa1 > fa2 ? fa[fa1] = fa2 : fa[fa2] = fa1;
fa[fa1] = fa2;
}
}
int main(){
// freopen("data013.txt","r",stdin);
int n,m,k;
scanf("%d%d",&n,&m);
for(int i = 0; i < n; i++) fa[i] = i;
for(int i = 0; i < m; i++){
int x,y;
scanf("%d%d",&x,&y);
pa[x][y] = pa[y][x] = 1;
}
scanf("%d",&k);
for(int i = 0; i < k; i++){scanf("%d",&del[i]); vis[del[i]] = 1;}
cnt = n - k;
for(int i = 0;i < n; i++) //给出的城市删除后的初始并查集
if(vis[i] != 1)
for(int j = 0; j < n; j++){
if(vis[j] != 1 && pa[i][j]) Union(i,j);
}
ans[k] = cnt;
for(int i = k - 1; i >= 0; i--){ //每添加一个城市更新并查集
int ii = del[i];
cnt++;
vis[ii] = 0;
for(int j = 0; j < n; j++)
if(vis[j] != 1 && pa[ii][j]) Union(ii,j);
ans[i] = cnt; //添加后连通块数目
}
for(int i = 0; i < k ; i++) printf("%d ",ans[i]);
printf("\n");
for(int i = 0; i < k; i++){
if(ans[i] < ans[i + 1]) printf("Red Alert: City %d is lost!\n",del[i]);
else printf("City %d is lost.\n",del[i]);
}
if(k == n) printf("Game Over.");
}
其他
L2_001 紧急救援
思路:dijkstra算法。
用num[i]和w[i]表示从出发点到i结点拥有的路的条数,以及能够找到的救援队的数目。当进行松弛操作的时候,更新dis[v], num[v],w[v]。
用pre[i]存储前一个结点,使用栈打印路径。
#include <cstdio>
#include <stack>
using namespace std;
const int maxn = 505;
const int inf = 1000;
int n, m, S, D;
int dis[maxn], team[maxn], e[maxn][maxn], num[maxn], w[maxn], pre[maxn];
bool vis[maxn];
int main() {
scanf("%d%d%d%d", &n, &m, &S, &D);
for(int i = 0; i < n; i++)
scanf("%d", &team[i]);
for(int i = 0;i < n;i++) dis[i] = inf;
int a, b, c;
while(m) {
scanf("%d%d%d", &a, &b, &c);
e[a][b] = c;
e[b][a] = c;
m--;
}
dis[S] = 0;
w[S] = team[S];
num[S] = 1;
for(int i = 0; i < n; i++) {
int x, minn = inf;
for(int j = 0; j < n; j++) // 找dis最小的点
if(!vis[j] && dis[j] < minn) minn = dis[x=j];
vis[x] = true;
for(int v = 0; v < n; v++) {
if(!vis[v] && e[x][v] != 0) {
if(dis[x] + e[x][v] < dis[v]) {
dis[v] = dis[x] + e[x][v];
num[v] = num[x];
w[v] = w[x] + team[v];
pre[v] = x;
} else if(dis[x] + e[x][v] == dis[v]) {
num[v] = num[v] + num[x];
if(w[x] + team[v] > w[v]) {
w[v] = w[x] + team[v];
pre[v] = x;
}
}
}
}
}
printf("%d %d\n", num[D], w[D]);
stack<int>path; //打印
path.push(D);
int temp=D;
while(pre[temp]!=S){
temp=pre[temp];
path.push(temp);
}
printf("%d",S);
while(!path.empty()){printf(" %d",path.top());path.pop();}
return 0;
}
L2_003 月饼
思路:贪心。每次选取单位价格最高的月饼进行售卖。
注意点:1. 题面给的样例是整数,但并不能就完全定义成整数,应仔细分析各种量的类型
2.要考虑到需求量大于所有月饼的量之和。之前没有考虑,故会重复的加上第一种月饼的量,因为order[i]初始的是0
复杂度:O(nlogn)
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1005;
double amt[maxn],prc[maxn];
int ord[maxn];
bool cmp(const int i,const int j){
double a=prc[i]/amt[i];
double b=prc[j]/amt[j];
if(a!=b) return a>b;
else return amt[i]<amt[j];
}
int main(){
int n;
double m;
scanf("%d%lf",&n,&m);
for(int i=0;i<n;i++) scanf("%lf",&amt[i]);
for(int i=0;i<n;i++) scanf("%lf",&prc[i]);
for(int i=0;i<n;i++) ord[i]=i;
sort(ord,ord+n,cmp); //间接排序,排索引
int cnt=0;
double rest=m,sum=0;
while(rest>=amt[ord[cnt]]&&cnt<n){ //考虑需求量大于所有月饼的量之和
sum=sum+prc[ord[cnt]];
rest=rest-amt[ord[cnt]];
cnt++;
}
if(cnt!=n){
double part=rest/amt[ord[cnt]]*prc[ord[cnt]];
sum=sum+part;
}
printf("%.2lf",sum);
}
L2_008 最长对称子串
思路一:枚举对称中心然后向外拓展,拓展到不能再拓展为止。由于子串字符是奇数个和偶数个不大一样,所以分了类。
复杂度:O(n^2)
思路二:Manacher 算法
#include<cstdio>
#include<iostream>
using namespace std;
int main(){
// freopen("data008.txt","r",stdin);
string str;
getline(cin,str);
int len=str.length();
int omaxm=0,omaxl=0; //奇数个
for(int i=0;i<len;i++){
int j=0;
while(i-j>=0&&i+j<=len){
if(str[i-j]==str[i+j]) j++;
else break;
}
if(2*j-1>omaxl){omaxl=2*j-1;omaxm=i;}
}
int emaxm=0,emaxl=0; //偶数个
for(int i=0;i<len-1;i++){
int j=0;
while(i-j>=0&&i+j+1<=len){
if(str[i-j]==str[i+j+1]) j++;
else break;
}
if(2*j>emaxl){emaxl=2*j;emaxm=i;}
}
printf("%d",emaxl>omaxl?emaxl:omaxl);
}
L2_009 抢红包
思路:在读入的时候就可以对发红包的人(发出的钱)和抢到红包的人(对抢到的红包个数和红包金额进行计数)进行处理。
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=10005;
const int maxk=25;
struct person{
int mon,cnt;
}p[maxn];
int ind[maxn];
bool cmp(int i,int j){
return p[i].mon>p[j].mon||(p[i].mon==p[j].mon&&p[i].cnt>p[j].cnt)||(p[i].mon==p[j].mon&&p[i].cnt==p[j].cnt&&i<j);
}
int main(){
// freopen("data009.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
ind[i]=i;
int k;
scanf("%d",&k);
int sum=0;
for(int j=0;j<k;j++){
int ID,amt;
scanf("%d%d",&ID,&amt);
p[ID].mon+=amt;
p[ID].cnt++;
sum+=amt;
}
p[i].mon-=sum;
}
sort(ind+1,ind+n+1,cmp);
for(int i=1;i<=n;i++){
printf("%d %.2lf\n",ind[i],p[ind[i]].mon/100.0);
}
}
L2_012 关于堆的判断
思路:先建堆,在用下标关系进行判断。 在寻找某值对应的下标时,采用类似于二叉树搜索的方法减少搜索规模。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int n,c1,c2;
int H[maxn];
void sswap(int i ,int j){
int t = H[i];
H[i] = H[j];
H[j] = t;
}
void buildHeap(){ //建堆
for(int i = 1;i <= n;i++){
int p = i;
while(p > 1){
if(H[p] < H[p/2]){sswap(p,p/2); p = p / 2;}
else break;
}
}
}
int find(int st, int k){ //找某个元素的下标
int i = st;
if( 2 * i > n) if(H[i] == k) return i; //该节点为叶节点
if( 2 * i == n) {if(H[i] == k) return i; else if(H[2 * i] == k) return 2 * i;} //该节点只有左子节点
while(2 * i < n){ //该节点有两个子节点
if(H[i] == k) return i;
if(H[2 * i] > k && H[2 * i + 1] <= k) return find(2 * i + 1, k); //若有一边堆顶元素大于(不能是大于等于),则可将范围缩小至1/2
else if(H[2 * i] <= k && H[2 * i + 1] > k) return find(2 * i, k);
else{ //两个子堆的堆顶都小于该元素
int in = find(2 * i,k);
if(in != -1) return in;
else {return find(2 * i + 1,k);}
}
}
return -1;
}
int main(){
freopen("data012.txt","r",stdin);
int m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%d",&H[i]);
buildHeap();
string str0;
getline(cin,str0);
while(m){
bool flag = false;
string str,sub;
getline(cin,str);
stringstream ss(str);
if(str[str.length()-1]=='t'){ss >> c1;if(c1 == H[1]) flag = true;}
else if(str[str.length()-1] == 's') {
ss >> c1;
ss >> sub;
ss >> c2;
int in1,in2;
in1 = find(1,c1); in2 = find(1,c2);
if( in1/2 == in2/2) flag = true;
}
else {
ss >> c1;
bool isp = true;
for(int i = 0; i < 4;i++){
ss >> sub;
if(i == 1 && sub[0] == 'a') isp = false;
}
ss >> c2;
int in1,in2;
in1 = find(1,c1); in2 = find(1,c2);
if(isp){if(in1 == in2 / 2) flag = true;} //之前少了括号
else if(in2 == in1 / 2) flag = true;
}
printf("%c\n",flag?'T':'F');
m--;
}
}
L2_014 列车调度
思路:用一个数组存储每条铁轨最后的列车编号,规定每次处理待加入的列车时,要么加入一条新铁轨,要么插入到已存在铁轨中,此铁轨满足最后的列车编号与之最接近且略小于它。这样就可以使用二分法确定插入位置了。
tip:一开始是遍历已有铁轨,将列车插入到第一个小于它的铁轨上,超时。
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=100005;
int rail[maxn];
int main(){
int n,cnt=0;
scanf("%d",&n);
for(int i=0;i<n;i++){
int x;
scanf("%d",&x);
if(cnt==0||rail[cnt-1]<x) rail[cnt++]=x;
else{
int l=0, r=cnt-1;
while(l<r){
int mid=l+(r-l)/2;
if(rail[mid]>x)
r=mid-1;
else l=mid+1;
}
rail[l]=x;
}
}
printf("%d",cnt);
}
L2_015 互评成绩
思路:冒泡排序
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=10005;
double grade[maxn];
void swap(int x,int y){
double tmp=grade[x];
grade[x]=grade[y];
grade[y]=tmp;
}
void bubble(int m,int n){ //冒泡排序进行前m个的排序
for(int i=0;i<m;i++){
for(int j=n-1;j>i;j--)
if(grade[j]>grade[j-1]) swap(j,j-1);
}
}
int main(){
// freopen("data015.txt","r",stdin);
int n,m,k;
scanf("%d%d%d",&n,&k,&m);
for(int i=0;i<n;i++){
double pg[10];
memset(pg,0,sizeof(pg));
for(int j=0;j<k;j++) scanf("%lf",&pg[j]);
sort(pg,pg+k);
double sum=0.0;
for(int x=1;x<k-1;x++) sum+=pg[x];
grade[i]=sum/(k-2);
}
bubble(m,n);
printf("%.3lf",grade[m-1]);
for(int i=m-2;i>=0;i--) printf(" %.3lf",grade[i]);
}
L2_017 人以群分
思路:先排序,后分组:偶数个人均分,奇数个人使活跃度大的一组多一人。
#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
const int INF=-2100000000;
int p[maxn];
bool cmp(int i,int j){
return i>j;
}
int main(){
// freopen("data017.txt","r",stdin);
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>p[i];
}
sort(p,p+n,cmp);
int summ=0;
for(int i=0;i<n;i++) summ+=p[i];
if(n%2==0){
int sum1=0;
for(int j=0;j<=(n-1)/2;j++){
sum1+=p[j];
}
int sum2=summ-sum1;
int dif=sum1-sum2;
printf("Outgoing #: %d\n",n/2);
printf("Introverted #: %d\n",n/2);
printf("Diff = %d",dif);
}
else{
int sum1=0;
for(int j=0;j<=(n-1)/2;j++){
sum1+=p[j];
}
int sum2=summ-sum1;
int dif1=sum1-sum2;
printf("Outgoing #: %d\n",n/2+1);
printf("Introverted #: %d\n",n/2);
printf("Diff = %d",dif1);
}
}
L2_020 功夫传人
思路:计算出每位得道者与祖师爷之间相隔的代数,进而得到其功力。
#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
int sf[maxn]; //每人的师傅
int ddz[maxn]; //得道者的索引
int glzz[maxn]; //每位得道者功力增长
int ds;
void findds(int i){ //确定得道者与祖师爷之间相隔代数
if(sf[i]==0) {ds++;return;}
ds++;
findds(sf[i]);
}
int main(){
sf[0]=0;
// freopen("002.txt","r",stdin);
int n;
double z,r;
scanf("%d %lf %lf",&n,&z,&r);
int cnt=0;
for(int i=0;i<n;i++){
int k;
scanf("%d",&k);
if(k==0){ddz[cnt++]=i;scanf("%d",&glzz[i]);}
else{
for(int j=0;j<k;j++){
int x;
scanf("%d",&x);
sf[x]=i;
}
}
}
double sum=0.0;
for(int i=0;i<cnt;i++){
ds=0;
findds(ddz[i]);
double ysgl=z;
for(int j=0;j<ds;j++){
ysgl=ysgl*(100-r)/100;
}
ysgl*=glzz[ddz[i]];
sum+=ysgl;
}
int summ=(int)(sum);
printf("%d",summ);
}