话说也蛮久没写小结了,主要这两次考试失分严重,还是总结下吧。
10.16
T1 小奇挖矿2 100/0
【题目背景】
小奇飞船的钻头开启了无限耐久+精准采集模式!这次它要将原矿运到泛光之源的矿石交易市场,以便为飞船升级无限非概率引擎。
【问题描述】
现在有m+1个星球,从左到右标号为0到m,小奇最初在0号星球。
有n处矿体,第i处矿体有ai单位原矿,在第bi个星球上。
由于飞船使用的是老式的跳跃引擎,每次它只能从第x号星球移动到第x+4号星球或x+7号星球。每到一个星球,小奇会采走该星球上所有的原矿,求小奇能采到的最大原矿数量。
注意,小奇不必最终到达m号星球。
【输入格式】
第一行2个整数n,m。
接下来n行,每行2个整数ai,bi。
【输出格式】
输出一行一个整数,表示要求的结果。
【样例输入】
3 13
100 4
10 7
1 11
【样例输出】
101
【样例解释】
第一次从0到4,第二次从4到11,总共采到101单位原矿。
【数据范围】
对于20%的数据 n=1,m<=10^5
对于40%的数据 n<=15,m<=10^5
对于60%的数据 m<=10^5
对于100%的数据 n<=10^5,m<=10^9,1<=ai<=10^4,1<=bi<=m
一开始想的按照每个星球去dp,发现m太大,之后打表发现超过18的可以缩小为18,之后按照矿石位置dp,然而考场读写文件写挂,我果然菜包了。
代码
#include<bits/stdc++.h> using namespace std; struct node{ int data;//矿石的数量 int num;//编号 }a[100005]; int n,m,ans; int news[2000005],f[2000005]; bool cmp(node x,node y){ return x.num<y.num; } int main(){ scanf("%d %d",&n,&m); memset(f,-1,sizeof(f)); f[0]=0; for(int i=1;i<=n;++i){ scanf("%d %d",&a[i].data,&a[i].num); if(!a[i].num) f[0]+=a[i].data; } sort(a+1,a+1+n,cmp); int tmp=0; for(int i=1;i<=n;++i){ if(a[i].num-a[i-1].num>17){//压缩路径 tmp+=18; news[tmp]+=a[i].data; }else{ tmp+=a[i].num-a[i-1].num; news[tmp]+=a[i].data; } } //new是新数组,压缩完后的 for(int i=0;i<=tmp;++i){//dp if(f[i]!=-1){ f[i+4]=max(f[i+4],f[i]+news[i+4]);//走4或者走7 f[i+7]=max(f[i+7],f[i]+news[i+7]); } ans=max(ans,f[i]); } printf("%d",ans); return 0; }
T2 小奇的矩阵 70/70
【题目背景】
小奇总是在数学课上思考奇怪的问题。
【问题描述】
给定一个n* m的矩阵,矩阵中的每个元素aij为正整数。
接下来规定
1.合法的路径初始从矩阵左上角出发,每次只能向右或向下走,终点为右下角。
2.路径经过的n+m-1个格子中的元素为A1,A2…A(n+m-1),Aavg为Ai的平均数,路径的V值为(n+m-1)* ∑(Ai-Aavg) ^2
(1<=i<=n+m-1)
求V值最小的合法路径,输出V值即可,有多组测试数据。
输入
第一行包含一个正整数T,表示数据组数。
对于每组数据:
第一行包含两个正整数n和m,表示矩阵的行数和列数。
接下来n行,每行m个正整数aij,描述这个矩阵。
输出
对于每次询问,输出一行一个整数表示要求的结果。
样例输入
1
2 2
1 2
3 4
样例输出
14
提示
【数据范围】
对于30%的数据 n<=10,m<=10
有另外40%的数据 n<=15 m<=15,矩阵中的元素不大于5
对于100%的数据 T<=5,n<=30,m<=30,矩阵中的元素不大于30
一道推公式然后dp的题,考场推出公式然而不会敲dp,加了个不知道对不对的剪枝,对拍了200多组数据都对到了就交了,dfs+玄学剪枝 70pt。
70pt
直接拿题目的式子 $ (n+m-1)\sum_{i = 1}^{n+m-1}(A_i-A_{avg}) $ 拆开化简之后得
$ (n+m-1)[ \sum_{i = 1}^{n}{A_i}^2-(n+m-1)A_{avg}^2 ] $
$ A_{avg}=\frac{\sum_{i = 1}^{n+m-1}A_i}{n+m-1} $
带入得到 $ (n+m-1)\sum_{i = 1}^{n+m-1}{A_i}^2- (\sum_{i = 1}^{n+m-1} A_i)^2 $
然后直接暴力搜索即可
#include<bits/stdc++.h> using namespace std; int T,n,m,a[35][35]; long long ans=(long long)1<<60,add=0; void dfs(int x,int y,long long data,long long sum){ if(x>n||y>m) return; if(data*(x+y-1)-sum*sum>=ans) return;//剪枝 if(x==n&&y==m){ add++; ans=min(ans,data*(n+m-1)-sum*sum); return; } if(x<n) dfs(x+1,y,data+a[x+1][y]*a[x+1][y],sum+a[x+1][y]); if(y<m) dfs(x,y+1,data+a[x][y+1]*a[x][y+1],sum+a[x][y+1]); } int main(){ scanf("%d",&T); while(T--){ scanf("%d %d",&n,&m); ans=(long long)1<<60; add=0; for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ scanf("%d",&a[i][j]); } } dfs(1,1,a[1][1]*a[1][1],a[1][1]); printf("%lld\n",ans); } return 0; }
正解
设计 $ f(s,i,j) $表示到 \((i,j)\),和为 $ s $ 的最小的 $ \sum_{i = 1}^{n+m-1}{A_i}^2 $,最后枚举 $ s $ 取 $ f(s,i,j)-s^2 \(的最小值,复杂度也是\) O(T* (\sum_{i = 1}^{n+m-1} A_i)^2 * n^2) $
#include<bits/stdc++.h> using namespace std; int T,n,m,a[50][50],f[2000][50][50]; int main(){ scanf("%d",&T); while(T--){ scanf("%d %d",&n,&m); for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ scanf("%d",&a[i][j]); } } memset(f,0x3f,sizeof(f)); f[a[1][1]][1][1]=a[1][1]*a[1][1]; for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ if(i==1&&j==1) continue; for(int k=a[i][j];k<=1800;++k){ f[k][i][j]=min(f[k-a[i][j]][i-1][j],f[k-a[i][j]][i][j-1])+a[i][j]*a[i][j]; } } } long long ans=(long long)1<<60; for(int i=0;i<=1800;++i){ ans=min(ans,(long long)(n+m-1)*f[i][n][m]-i*i); } printf("%lld\n",ans); } return 0; }
T3 小奇的仓库 50/0
【题目背景】
小奇采的矿实在太多了,它准备在喵星系建个矿石仓库。令它无语的是,喵星系的货运飞船引擎还停留在上元时代!
【问题描述】
喵星系有n个星球,星球以及星球间的航线形成一棵树。
从星球a到星球b要花费[dis(a,b) Xor M]秒。(dis(a,b)表示ab间的航线长度,Xor为位运算中的异或)
为了给仓库选址,小奇想知道,星球i(1<=i<=n)到其它所有星球花费的时间之和。
输入
第一行包含两个正整数n,M。
接下来n-1行,每行3个正整数a,b,c,表示a,b之间的航线长度为c。
输出
n行,每行一个整数,表示星球i到其它所有星球花费的时间之和。
样例输入
4 0
1 2 1
1 3 2
1 4 3
样例输出
6
8
10
12
一道dp题,现在还不会,考场打的lca+dp,预计得分50pt,然而读写文件又一次错了,并且lca的t也弄错了。。
30分可以直接lca求距离然后异或即可
另外20分dp,我们发现每个点它到各个点的距离只于它和它的父节点之间的距离有关,我们把边分成两块,一部分是它到它父节点(不包括父节点)以上的点,这里是多出来的边,另外一部分是父节点到它以下的点(不包括它),之后推出公式,直接dp即可。
50pt
#include<bits/stdc++.h> using namespace std; const int size=100010; int f[size][20],d[size],ds[size],v[size],book[size],g[size]; int ver[2*size],Next[2*size],head[size],edge[2*size]; int m,n,tot,t,ans; int x,y,z; queue<int> q; inline int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } void add(int x,int y,int z){ ver[++tot]=y;edge[tot]=z;Next[tot]=head[x];head[x]=tot; } void bfs(){ q.push(1);d[1]=1;ds[1]=0; while(q.size()){ int x=q.front();q.pop(); for(register int i=head[x];i;i=Next[i]){ int y=ver[i]; if(d[y]) continue; d[y]=d[x]+1; ds[y]=ds[x]+edge[i]; f[y][0]=x; for(register int j=1;j<=t;++j){ f[y][j]=f[f[y][j-1]][j-1]; } q.push(y); } } } int lca(int x,int y){ if(d[x]>d[y]) swap(x,y); for(register int i=t;i>=0;--i){ if(d[f[y][i]]>=d[x]){ y=f[y][i]; } } if(x==y) return x; for(register int i=t;i>=0;--i){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } void dfs(int x){ book[x]=1;v[x]=1; for(register int i=head[x];i;i=Next[i]){ int y=ver[i]; if(book[y]) continue; dfs(y); v[x]+=v[y]; } return; } void solve(int x){ book[x]=1; for(register int i=head[x];i;i=Next[i]){ int y=ver[i]; if(book[y]) continue; g[y]=(n-2*v[y])*(ds[y]-ds[x])+g[x];//按照公式推过去 solve(y); } return; } int main(){ //freopen("warehouse.in","r",stdin); //freopen("warehouse.out","w",stdout); n=read();m=read(); t=((int)(log(n)/log(2)))+1; for(register int i=1;i<n;++i){ x=read();y=read();z=read(); add(x,y,z); add(y,x,z); } bfs(); if(n>2000){ memset(book,0,sizeof(book)); dfs(1); for(int i=1;i<=n;++i){ g[1]+=ds[i]^m;//根节点到所有点的距离 } memset(book,0,sizeof(book)); solve(1); for(int i=1;i<=n;++i){ printf("%d\n",g[i]); } }else{ for(register int i=1;i<=n;++i){ long long ans=0; for(register int j=1;j<=n;++j){ if(i!=j){ int dis=ds[i]+ds[j]-2*ds[lca(i,j)]; ans+=dis^m; } } printf("%lld\n",ans); } } return 0; }
一天考下来,得出结论我菜爆了,考场总是遗漏一些细节上的问题,比如读写文件,变量重复使用或是忘了赋值之类的
10.17
开题出现失误,并且一直写一题没写出来,导致时间不够了,写的太急了,犯了一堆小错误。
T1 小奇挖矿3 30/0
一看就dp,考场一开始去想正解,想了一个半小时,还是没想出来。。最后五分钟敲了个暴力,还敲错了,应该先枚举完再去找最近的地点,而不是边枚举边找。
30pt
#include<bits/stdc++.h> using namespace std; int n,m,d[250],a[250][250],dis[250][250]; long long ans=1<<30; bool book[250]; void dfs(int x,long long data){ if(x>n){ for(int i=1;i<=n;++i){ if(book[i]) continue; int op=1<<30; for(int j=1;j<=n;++j){ if(i!=j&&book[j]){ op=min(op,dis[i][j]); } } if(op==1<<30) return; data+=d[op]; } ans=min(ans,data); return; } dfs(x+1,data); book[x]=1; dfs(x+1,data+m); book[x]=0; } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<n;++i) scanf("%d",&d[i]); memset(dis,0x3f,sizeof(dis)); for(int i=1;i<n;++i){ int x,y; scanf("%d %d",&x,&y); a[x][y]=a[y][x]=1; dis[x][y]=dis[y][x]=1; } for(int k=1;k<=n;++k){ for(int i=1;i<=n;++i){ for(int j=1;j<=n;++j){ if(dis[i][j]>dis[i][k]+dis[k][j]){ dis[i][j]=dis[i][k]+dis[k][j]; } } } } dfs(1,0); printf("%lld",ans); return 0; }
100pt
我们定义 \(f[i][j]\) 表示由 $ i $ 到 距离最近的点为 $ j $( $ j $ 点当前还没有设置仓库)时 $ i $ 的所有子树所需要的费用和。
转移的时候枚举 $ i $ 的儿子,假设为 $ v $ 如果距离 $ v $ 最近仓库也是 $ j $ 直接转移过来就行,如果不是,那么意味着 $ v $ 的子树中存在一个点 $ k $ 是距离 $ v $ 最近的仓库那么枚举 $ k $即可,转移的时候加上设立仓库所需要的费用。
#include<bits/stdc++.h> using namespace std; const int size=250; int n,m,d[size],dis[size][size],f[size][size]; long long ans=1<<30; int ver[size*2],head[size],Next[size*2],tot; void add(int x,int y){ ver[++tot]=y;Next[tot]=head[x];head[x]=tot; } void dfs(int u,int v,int fa,int d){//不可以用Floyd,求子树的最短距离 dis[u][v]=d; for(int i=head[v];i;i=Next[i]){ int y=ver[i]; if(y==fa) continue; dfs(u,y,v,d+1); } } void dp(int x,int fa){ for(int i=1;i<=n;++i){ f[x][i]=d[dis[x][i]]; } for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa) continue; dp(y,x); int minn=1<<30; for(int j=1;j<=n;++j){ minn=min(minn,f[y][j]+m);//不同 } for(int j=1;j<=n;++j){ f[x][j]+=min(minn,f[y][j]);//转移 } } } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<n;++i) scanf("%d",&d[i]); memset(dis,0x3f,sizeof(dis)); for(int i=1;i<n;++i){ int x,y; scanf("%d %d",&x,&y); add(x,y);add(y,x); dis[x][y]=dis[y][x]=1; } for(int i=1;i<=n;++i) dfs(i,i,0,0); dp(1,0); for(int i=1;i<=n;++i){ ans=min(ans,(long long)f[1][i]+m); } printf("%lld",ans); return 0; } /* 8 10 2 5 9 11 15 19 20 1 4 1 3 1 7 4 6 2 8 2 3 3 5 */
T2 小奇分糖果 0/0
考场第一题弄太久了,没时间了。
T3 小奇走迷宫 10/10
考场知道dp,不会敲,暴力dfs 10pt。
10pt
#include<bits/stdc++.h> using namespace std; const int dx[5][2]={{0,-1},{0,1},{1,0},{-1,0}}; int n,m,k,ans=1<<30,a[150][150]; bool book[250][250]; char str; void dfs(int x,int y,int sum,int data,int dir){ if(x>n||x<1||y>m||y<1) return; if(data>=ans) return; if(sum==k){ ans=min(data,ans); return; } for(int i=0;i<4;++i){ int newx=dx[i][0]+x; int newy=dx[i][1]+y; if(newx<=n&&newy<=m&&newx>=1&&newy>=1&&a[newx][newy]!=1&&book[newx][newy]!=1){ if(a[newx][newy]==2){ if(!book[newx][newy]){ book[newx][newy]=1; if(dir==i) dfs(x,y,sum+1,data,i); else dfs(x,y,sum+1,data+1,i); book[newx][newy]=0; } } book[newx][newy]=1; if(dir==i) dfs(newx,newy,sum,data,i); else dfs(newx,newy,sum,data+1,i); book[newx][newy]=0; } } } int main(){ scanf("%d %d %d",&n,&m,&k); for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ scanf("%c",&str); if(str=='.') a[i][j]==0; else if(str=='#') a[i][j]=1; else j--; } } for(int i=1;i<=k;++i){ int x,y; scanf("%d %d",&x,&y); a[x][y]=2; } int x,y; scanf("%d %d",&x,&y); for(int i=0;i<4;++i){ dfs(x,y,0,1,i); } printf("%d",ans); return 0; }
T4 小奇的花园 50/0
这题应该是今天失误最大的一题了,敲完lca估分50pt,结果t重复定义,嗯爆零了。
50pt
从 $ x,y $ 朝 $ lca $ 往上走,边走边记数,最后判断 $ lca $ 是不是,如果是就减去 $ 1 $ (重复计算了)
#include<bits/stdc++.h> using namespace std; const int size=100010; int n,Q,a[size],ans; char str[10]; int ver[2*size],head[size],Next[2*size],tot,f[size][20],d[size]; int x,y,t,k; queue<int> q; void bfs(){ q.push(1);d[1]=1; while(q.size()){ int x=q.front();q.pop(); for(register int i=head[x];i;i=Next[i]){ int y=ver[i]; if(d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for(register int j=1;j<=t;++j){ f[y][j]=f[f[y][j-1]][j-1]; } q.push(y); } } } int lca(int x,int y){ if(d[x]>d[y]) swap(x,y); for(register int i=t;i>=0;--i){ if(d[f[y][i]]>=d[x]){ y=f[y][i]; } } if(x==y) return x; for(register int i=t;i>=0;--i){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } void add(int x,int y){ ver[++tot]=y;Next[tot]=head[x],head[x]=tot; } void dfs(int x,int fa,int ed){ if(a[x]==k) ans++; if(x==ed) return; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa||d[y]>d[x]) continue; dfs(y,x,ed); } } int main(){ scanf("%d %d",&n,&Q); t=((int)(log(n)/log(2)))+1;//一个t for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<n;++i){ scanf("%d %d",&x,&y); add(x,y); add(y,x); } bfs(); while(Q--){ scanf("%s",str); if(str[0]=='Q'){ scanf("%d %d %d",&x,&y,&k);//原先是t x^=ans;y^=ans;k^=ans; ans=0; int l=lca(x,y); dfs(x,x,l); dfs(y,y,l); if(a[l]==k) ans--; printf("%d\n",ans); } else{ scanf("%d %d",&x,&y); a[x^ans]=y^ans; } } return 0; }
这两天小错误频频出现,主要是考场的细致以及写题目时的心态,可能今天第一题把心态搞崩了导致后面比较着急,写题还是要静下心来,多细致检查。
lca再敲错我就***