题目名称 |
run |
homework |
cut |
输入 |
run.in |
homework.in |
cut.in |
输出 |
run.out |
homework.out |
cut.out |
每个测试点时限 |
1秒 |
1秒 |
2秒 |
内存限制 |
64MB |
64MB |
64MB |
测试点数目 |
10 |
10 |
10 |
每个测试点分值 |
10 |
10 |
10 |
是否有部分分 |
无 |
无 |
无 |
题目类型 |
传统 |
传统 |
传统 |
run
Background:
穿过时空隧道,小明来到了秦朝。我是谁?我在哪?
“哈哈哈,金科,发什么呆呢,你的刺杀已经失败了!”秦王躲在柱子后狂妄的笑。
一股热血在小明体内流动,他竟情不自禁地追了上去。
Description:
宫殿里一共有n根柱子,假设柱子在二维平面内的位置为(xi,yi)。金科现在在一号柱子,秦王现在在n号柱子。金科(小明)会飞檐走壁所以他从i号柱子到j号柱子的距离为min(|xi-xj|,|yi-yj|)。深知自己并不能刺杀秦王,所以小明知道事实上自己竭尽全力,也离秦王有一步之遥,所以小明想知道自己到达n号柱子的最短距离-1是多少,因为这将是他人生能走过的最后的路。
Input:
第一行一个数n。
接下来n-1行每行两个数x,y分别表示xi和yi。
Output:
一行一个数表示从1号柱子走到n号柱子的最短距离减去1的长度。
Sample input:
5
2 2
1 1
4 5
7 1
6 7
Sample output:
1
Hint:
1->2->4->5最短距离为2,答案为1。
对于30%的数据,n<=1000。
对于100%的数据,n<=200000,0<xi,yi<1e9
数据保证答案>0,不然很奇怪。。
题解:我们发现每个点分别于横纵坐标相邻的点连边就可以解决问题了,建边后直接跑最短路就可以了。最后不要忘记答案-1.(可以自己构图验证正确性)
用堆优化+dijistra的板子
#include <bits/stdc++.h> #include <algorithm> using namespace std; #define maxn 200005 #define maxm 1000005 bool vis[maxn]; int L,n,tot,head[maxn],dis[maxn]; inline void read(int &x){ x=0;int f=1;char s=getchar(); while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();} x*=f; } struct Point{ int x,y,id; }p[maxn]; struct edge{ int nxt,v,w; }G[maxm]; struct rp{ int id,key; rp(int a,int b){ id=a, key=b; } }; bool cmp2(Point a, Point b){ return a.x < b.x; } bool cmp1(Point a, Point b){ return a.y < b.y; } struct cmp{ bool operator()(const rp& a, const rp& b){ return a.key > b.key; } }; void add(int u, int v, int w){ G[++tot].nxt = head[u]; G[tot].w = w; G[tot].v = v; head[u] = tot; G[++tot].nxt = head[v]; G[tot].w = w; G[tot].v = u; head[v] = tot; } void dijistra(){ memset(dis, 127, sizeof(dis)); priority_queue <rp,vector<rp>,cmp> q; q.push(rp(1, 0)); dis[1] = 0; while(!q.empty()){ rp u = q.top(); q.pop(); if(vis[u.id])continue; vis[u.id] = 1; for(int j = head[u.id]; j; j = G[j].nxt){ int v = G[j].v; if(dis[v] > dis[u.id] + G[j].w){ dis[v] = dis[u.id] + G[j].w; q.push(rp(v, dis[v])); } } } } int main() { freopen("run.in","r",stdin); freopen("run.out","w",stdout); read(n); for(int i = 1; i <= n; i++) read(p[i].x), read(p[i].y), p[i].id = i; sort(p+1, p+1+n, cmp2); for(int i = 1; i < n; i++) add(p[i].id, p[i+1].id, p[i+1].x - p[i].x); sort(p+1, p+1+n, cmp1); for(int i = 1; i < n; i++) add(p[i].id, p[i+1].id, p[i+1].y - p[i].y); dijistra(); printf("%d ",dis[n]-1); }
homework
Background:
小哪吒在开学前一天补作业。。。
Description:
明天就要开学了!所以小哪吒需要尽快写完作业。现在,他有n份作业需要写,因为小哪吒精通变化,他可以再变出两只手来,所以他可以同时写两份作业。小哪吒两双手写同一份作业的时间是不一样的。为了节约时间,他用火眼睛睛看出了两双手写同一份作业分别需要ai,bi的时间。同样的,为了节约时间,他不会更改做题的顺序,即他会从第一份作业一直做到第n份作业。他唯一不知道的是,他能写完作业的最短时间。你能帮他求出来吗?
Input:
第一行一个数n表示作业的数目。
接下来n行每行两个数ai,bi分别表示小哪吒两双手写这份作业的时间。
Output:
一行一个数表示写作业的最短时间。
Sample input:
3
1 3
1 3
1 3
Sample output:
3
Hint:
对于样例,用第一双手做完所有作业。
对于40%的数据,n<=20。
对于100%的数据,n<=2000。ai,bi<=3000。
题解:我们用dp[i][0/1][j]表示去做第i份作业用第0/1只手时,另外一双手还需要做j的时间才能做完作业的最小时间;
dp[i][j][k] = min( dp[i - 1][ j ][ k+a[i - 1][ j ] ] + a[i - 1][ j ], dp[i - 1][ j^1 ][ a[i - 1][ j^1 ] ] , 但我们有可能在0手有空时不一定用0手而是等1手完了用1手,
而这样当 k+a[i - 1][ j ] = 0我们刚好就去用了空闲的手,故我们反着推就可以了,因为dp[i][j][k] k=0 时有可能那只手已经休息了很久了;
注意dp[i]中第i份作业还没做,所以我们最后要加上剩下的时间
#include <bits/stdc++.h> using namespace std; #define maxn 2005 int dp[maxn][2][3005], a[maxn][2]; inline void read(int &x){ x=0;int f=1;char s=getchar(); while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();} x*=f; } int main(){ ///freopen("homework.in","r",stdin); //freopen("homework.out","w",stdout); int n; read(n); for(int i = 1; i <= n; i++) read(a[i][0]), read(a[i][1]); memset(dp, 0x3f, sizeof(dp)); dp[1][0][0] = dp[1][1][0] = 0; for(int i = 1; i <= n; i++) for(int j = 0; j < 2; j++) for(int k = 0; k <= 3000; k++){ dp[i+1][j][max(0, k-a[i][j])] = min(dp[i+1][j][max(0, k-a[i][j])], dp[i][j][k]+a[i][j]); dp[i+1][j][max(0, a[i][j^1]-k)] = min(dp[i+1][j][max(0, a[i][j^1]-k)] , dp[i][j^1][k]+k); } int ans = 1e9; for(int j = 0; j < 2; j++) for(int k = 0; k <= 3000; k++) ans = min(ans, dp[n][j][k] + max(k, a[n][j])); printf("%d ",ans); }
cut
Background:
伐木工Zxr非常懒惰了,所以他在伐木的时候只会找准木头被虫蛀过的腐朽的地方砍下去。
Description:
一块有N-1处被虫蛀过的地方,假设被虫蛀过的地方长度不计。这n-1个虫蛀将木头分成了n块,题目将依次给出这n块木头的长度。懒惰的zxr最多只想砍m次,并且希望可以借此把这块木头分得尽量均匀,即希望使砍伐后连续的木块中最长的尽量短。这个时候聪明的你跳了出来“我不仅能算出这个最短长度,我还能算出方案数!”
Input:
第一行两个数n,m。
接下来一行n个数分别表示这N块木头的长度。
Output:
一行两个数分别表示最短的最长长度和方案数。
Sampleinput:
3 2
1 1 10
Sampleoutput:
10 2
Hint:
两种砍的方法: (1)(1)(10)和(1 1)(10)。
对于30%的数据,n<=10,
对于100%的数据,n<=50000,
0<=m<=min(n-1,1000),1<=Li<=1000.
题解:
第一问是一个十分显然的二分,贪心Check(),很容易就能求出最小的最大长度 Len 。
第二问求方案总数,使用 DP 求解。
使用前缀和,令 Sum[i] 为前i根木棍的长度和。
令 f[i][j] 为前i根木棍中切 j 刀,并且满足最长长度不超过 Len的方案数,那么:
状态转移方程: f[i][j] = Σ f[k][j-1] ((1 <= k <= i-1) && (Sum[i] - Sum[k] <= Len))
这样的空间复杂度为 O(nm) ,时间复杂度为 O(n^2 m) 。显然都超出了限制。
下面我们考虑 DP 的优化。
1) 对于空间的优化。
这个比较显然,由于当前的 f[][j] 只与 f[][j-1] 有关,所以可以用滚动数组来实现。
f[i][Now] 代替了 f[i][j] , f[i][Now^1] 代替了 f[i][j-1] 。为了方便,我们把 f[][Now^1] 叫做 f[][Last] 。
这样空间复杂度为 O(n) 。满足空间限制。
2) 对于时间的优化。
考虑优化状态转移的过程。
对于 f[i][Now] ,其实是 f[mink][Last]...f[i-1][Last] 这一段 f[k][Last] 的和,mink 是满足 Sum[i] - Sum[k] <= Len 的最小的 k ,那么,对于从 1 到 n 枚举的i,相对应的 mink 也一定是非递减的(因为 Sum[i] 是递增的)。我们记录下 f[1][Last]...f[i-1][Last] 的和Sumf,mink 初始设为 1,每次对于i将 mink 向后推移,推移的同时将被舍弃的 p 对应的 f[p][Last] 从Sumf中减去。那么 f[i][Now] 就是Sumf的值。
这样时间复杂度为 O(nm) 。满足时间限制。
#include <bits/stdc++.h> using namespace std; #define maxn 50005 #define mod 10007 int dp[maxn][2], a[maxn], sum[maxn], L, n, m, Ml; inline void read(int &x){ x=0;int f=1;char s=getchar(); while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();} x*=f; } bool check(int mid){ int ll = 0, i = 0, group = 1, flag = 0; while(i <= n){ if(ll + a[++i] <= mid)ll += a[i]; else if(a[i] > mid)return false; else { group++; if(group > m + 1)return false; ll = a[i]; } } return true; } void bs(){ int l = Ml, r = sum[n]; while(l != r){ int mid = (l + r) >> 1; if(check(mid))r = mid; else l = mid+1; } L = r;//取大的 printf("%d ",L); } void Dp(){ int sumf = 0, mink = 0, ans = 0; int k = 0; for(int j = 0; j <= m; j++){ sumf = 0; mink = 1;//一定要记得更新,忘了更新这个地方,WA了好多次 for(int i = 1; i <= n; i++){ if(!j){ if(sum[i] <= L)dp[i][k] = 1; } else { while(mink < i && sum[i] - sum[mink] > L){ sumf -= dp[mink][k^1]; sumf = (sumf + mod) % mod; mink++; } dp[i][k] = sumf; } sumf = (sumf + dp[i][k^1]) % mod; } ans += dp[n][k]; ans %= mod; k^=1;//滚动数组 } printf("%d ",ans); } int main(){ freopen("cut.in","r",stdin); freopen("cut.out","w",stdout); read(n),read(m); for(int i = 1; i <= n; i++){ read(a[i]); Ml = max(Ml, a[i]); sum[i] = sum[i-1] + a[i]; } bs(); Dp(); }