开学第十测
开学第十测
不知道为什么最近老是考试 都快烤糊了 可能是因为停课了吧 2333
宿舍里为了安空调,电都停了,天天的 都快热死了
其他校区都开用了,我们还没安 T_T
额。。。好像扯远了
T1 Simple
【问题描述】
对于给定正整数 n,m,我们称正整数 c 为好的,当且仅当存在非负整数 x,y,使得 n*x+m*y=c。
现在给出多组数据,对于每组数据,给定 n,m,q,求[1,q]内有多少个正整数不是好的。
【输入格式】
第一行,一个整数 T 表示数据组数。
接下来每行三个数,分别表示 n,m,q,即一组询问。
【输出格式】
对于每组数据,输出一行表示答案。
【样例输入】
2
78 100 4
70 3 34
【样例输出】
4
23
【数据范围及约定】
对于 30%的数据,n, m, q ≤ 100。
对于 60%的数据,n, m, q ≤ 105。
对于 100%的数据,n ≤ 105,m ≤ 109,q ≤ 1018,T ≤ 10。
考场思路:打暴力 不知道为啥他们的暴力都60,我的50 qwq
正解:数字较小时,可以枚举每个 c,直接用扩展欧几里得算出一个初始解,然后列个式子推一推。如果数字比较大,我们可以根据贝祖定理知道,如果 c 不是(m,n)的倍数,那么一定不是好的。否则我们考虑当 m 和 n 互质的时候,这时只要枚举 y∈[0,n-1],那么 y * m - x * n 在[1,q]之间的数一定都不是好的 。
推荐博客(讲扩展欧几里得的):http://www.cnblogs.com/linyujun/p/5167916.html
#include<algorithm> #include<cstring> #include<cstdio> #define LL long long using namespace std; int T; LL n, m, q; LL ans; int check(LL x, LL y) { return x <= y ? 1 : 0; } LL gcd(LL x, LL y) { return y == 0 ? x : gcd(y, x%y); } LL lcm(LL x, LL y) { return x / gcd(x, y) * y; } int main() { freopen("simple.in","r",stdin); freopen("simple.out","w",stdout); scanf("%d", &T); for(int i = 0; i < T; i++) { scanf("%I64d%I64d%I64d", &n, &m, &q); ans = q; if(check(n, q)) { LL s = q / n; if(check(m, q)) { LL t = q / m; LL tmp = q / lcm(n, m); ans = q - s - t + tmp; if(gcd(n, m) == n || gcd(n, m) == m) { printf("%I64d", ans); continue; } for(int i = 1; i <= s; i++) for(int j = 1; j <= t; j++) if(n*i + m*j <= q) ans--; } else ans = q - s; } else if(check(m, q)) { LL t = q / m; ans = q - t; } printf("%I64d\n", ans); } fclose(stdin); fclose(stdout); return 0; }
#include <stdio.h> #include <iostream> #include <algorithm> #include <memory.h> #include <string.h> #include <queue> using namespace std; typedef long long LL; typedef pair<int,int> mp; #define pb push_back const int inf = 1<<30; const int maxn = 10005; LL n,m,y,ans;int T; LL GCD(LL n,LL m) {return m?GCD(m,n%m):n;} LL updiv(LL n,LL m) {return n%m?n/m+1:n/m;} LL dndiv(LL n,LL m) {return n%m?n/m:n/m-1;} int main() { #ifndef ONLINE_JUDGE freopen("simple.in","r",stdin); freopen("simple.out","w",stdout); #endif for (scanf("%d",&T);T--;) { scanf("%I64d %I64d %I64d",&n,&m,&y); LL ans=y,t=GCD(n,m); n/=t;m/=t;y/=t;ans-=y; if (n>m) swap(n,m); for (int j=0;j<n;j++) { LL mink=max(1ll,updiv(j*m-y,n)); LL maxk=dndiv(j*m,n); if (mink<=maxk) ans+=maxk-mink+1; } printf("%I64d\n",ans); } return 0; }
T2 walk
【问题描述】
给定一棵 n 个节点的树,每条边的长度为 1,同时有一个权值 w。定义一条路径的权值为路径上所有边的权值的最大公约数。现在对于任意 i∈[1 , n],求树上所有长度为 i 的简单路径中权值最大的是多少。如果不存在长度为 i 的路径,则第 i 行输出 0。
【输入格式】
第一行,一个整数 n,表示树的大小。
接下来 n-1 行,每行三个整数 u,v,w,表示 u,v 间存在一条权值为 w 的边。
【输出格式】
对于每种长度,输出一行,表示答案。
【样例输入】
3
1 2 3
1 3 9
【样例输出】
9
3
0
【数据范围及约定】
对于 30%的数据,n ≤ 1000。
对于额外 30%的数据,w ≤ 100。
对于 100%的数据,n ≤ 4 * 105,1 ≤ u, v ≤ n,w ≤ 106。
考场思路:怎么又是树啊,不会啊不会 乱搞乱搞 然后->爆零 2333
正解:数据比较小的时候,直接枚举 O(n^2)条路径,dfs 一遍就好了。当 w 比较小的时候,我们可以枚举答案,然后走树上所有权值是 w倍数的边,一棵树上的最长链相信大家都会,这里不再赘述,复杂度是 O(w*n)。你发现枚举答案,然后统计最长链的时候,有很多边其实不需要访问,一条边只会在枚举他因子作为答案的时候才有用。所以我们只要把每条边拆成因子个数条边就好了,理论时间复杂度 O(n^1.5),但是因子的个数远远达不到这个数量级,所以只要常数不是太大,就可以通过本题。
#include <stdio.h> #include <iostream> #include <algorithm> #include <memory.h> #include <string.h> #include <vector> #include <time.h> using namespace std; typedef long long LL; typedef pair<int,int> mp; #define pb push_back const int inf = 1<<30; const int maxn = 1000005; const int N = 1000000; int ehead[maxn],ecnt; int head[maxn],cnt; int stk[maxn*2],top; struct edge{ int u,v,next; }rec[maxn],edg[maxn*2]; void add(int u,int v) { edg[++ecnt]=(edge){u,v,ehead[u]}; ehead[u]=ecnt; edg[++ecnt]=(edge){v,u,ehead[v]}; ehead[v]=ecnt; stk[++top]=u;stk[++top]=v; } void _add(int u,int v,int w) { rec[++cnt]=(edge){u,v,head[w]}; head[w]=cnt; } int n,ans[maxn],maxLen; int vis[maxn],idx; int dfs(int u) { int maxc=0;vis[u]=idx; for (int v,j=ehead[u];j;j=edg[j].next) if (vis[v=edg[j].v]!=idx) { int tmpc=dfs(v); maxLen=max(maxLen,maxc+tmpc+1); maxc=max(maxc,tmpc+1); } return maxc; } int main() { #ifndef ONLINE_JUDGE freopen("walk.in","r",stdin); freopen("walk.out","w",stdout); #endif scanf("%d",&n); for (int u,v,w,i=1;i<n;i++) scanf("%d %d %d",&u,&v,&w),_add(u,v,w); for (int i=1;i<=N;i++) { for (int j=i;j<=N;j+=i) for (int k=head[j];k;k=rec[k].next) add(rec[k].u,rec[k].v); maxLen=0;++idx; for (int v,j=1;j<=top;j++) if (vis[v=stk[j]]!=idx) dfs(v); for (int j=1;j<=top;j++) ehead[stk[j]]=0; ecnt=0;top=0;ans[maxLen]=i; } for (int i=n;i>=1;i--) ans[i]=max(ans[i],ans[i+1]); for (int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
T3 travel
【问题描述】
给定一个长度为 n 的格子序列 x 1 ,x 2 ,...,x n 。每一次 Lyra 可以选择向左跳到任意一个还没到过的位置,也可以向右跳到任意一个还没到过的位置。如果现在 Lyra 在格子 i,她下一步跳向格子 j,那么这次跳跃的花费为|x i -x j |。注意,跳意味着格子 i 和格子 j 中间其他的格子都不会被这次跳跃影响。并且,Lyra 不应该跳出边界。
Lyra 的初始位置在格子 s。Lyra 将会在到访过所有格子恰好一次之后,在某个位置停下来,这样就完成了任务。
Lyra 想知道如果她一共向左跳了 L 次,那么她要完成任务的最小总花费是多少,并希望你输出任意一种花费最小的方案。显然如果 Lyra 向左走了 L 次,那一定会向右走 n-L-l 次
特殊的,如果 Lyra 没有办法完成任务,请输出一行-1。
【输入格式】
第一行,三个整数 n, L,s,分别表示序列的大小,向左走的次数,和初始位置。
第二行,n 个数字,表示序列 x i 。
【输出格式】
第一行,一个数字,表示答案。
如果能完成任务,则第二行,输出 n-1 个数字,表示方案。注意,Lyra 初始的位置已经确定了,所以不要输出。
【样例输入】
3 1 2
1 2 3
【样例输出】
3
1 3
【样例解释与说明】
Lyra 一开始在 2 的位置,2->1->3 的路径中,Lyra 一共向左走了 1 次,花费为|2-1|+|1-3|=3。
【数据范围及约定】
测试点 1~2,n ≤ 8,0 ≤ x i ≤ 109。
测试点 3~8,n ≤ 20,0 ≤ x i ≤ 109。
测试点 9~10,n ≤ 2 * 105,x i = i。
测试点 11~20,n ≤ 2 * 105,0 ≤ x i ≤109。
对于所有数据,都满足 x 1 < x 2 <...<x n-1 <x n ,1 ≤ s ≤ n,0 ≤ L ≤ n-1。
考场思路:这是个什么鬼。。。1h后,好像从 s 开始一步一步往前蹦然后从 1 蹦到 s+1,在蹦到最后是最优的呢。再看看数据,嗯。。好像只有9~10这两个测试点可以酱紫做 2333,不过好歹也是一种做法,那就这样写呗。然后我就分了 s-l >1, =1, <1 三种情况去写,测大样例时发现,好像不大对的样子,然后又改改改......结果到最后硬是推了个式子,还不知道对不对qwq,so...最后只有10分
正解:数据很小的时候,分别可以爆搜每一个各自走的顺序或者每一个格子向左向右走的状态解决。然后 xi=i 比较好处理,讨论即可有一种贪心的方法是每次向一个方向走到底再向反方向走然后讨论,这样的思想和标算其实很相似,但是有一些问题。比较小的数据点,数据没有刻意卡这种贪心。
-1 的情况非常好处理显然当 l=0 但是 s≠1 的时候,是没有合法路径的。同理 l=n-1,s≠n 也是没有合法路径的。非常显然,这里不给出证明。
先考虑特殊的情况,当 s=1 的时候,显然答案的下界是 xn-x1,就是从最左边按次序一直跳到最右边,不过 L>0 的时候就不能这么做了。答案要求最小也就是说要尽量少走回头路。假如我们在 n 这个位置停下,那么中间就需要走一些回头的路类来用掉 L,我们把所有 i 到 i+1 之间的区间看成一个线段。跳的路径相当于对线段进行覆盖。显然所有的线段都必须覆盖至少 1 次,而至少有 L 个线段至少覆盖 3 次。下面给出证明。
假如某一次是从 i 向左跳,那么之前一定会从左侧跳到 i,肯定会覆盖[i-1,i]的线段,然后从 i 向左跳走回头路,又会覆盖[i-1,i]一次,最后因为一定要走到 n,所以在走完回头路后一定还会再向右跳回来,所以至少经过三次[i-1,i]。也就至少有 L 条线段被覆盖至少 3 次。这样我们就可以贪心的选取权值最小的 L 条线段计算作为答案了。显然这种情况下 L 越小,答案越优。
不过起始和结束的位置不一定在两端,那假设结束的位置是 e,根据对称性,不妨假设 s 在 e 的前面。显然 s 前面的线段和 e 后面的线段一定都至少经过两次,而且存在方案能让 s 前和 e 后一共用掉 n-e+s-1 个 L,并且每条线段只经过两次。这样不仅能减少[s,e]中间走 L 的次数,而且也让 s 和 e 两侧的花费尽量小。中间的部分事实上和刚才讨论的从 1 开始,n 为终点的情况是完全一样的。有一些边界情况需要注意,这里不作说明。
这样我们就可以直接枚举 e 来计算答案了,继而发现 e=i 和e=i+1 的情况只是有[i,i+1]这条线段覆盖次数限制的变化,其他线段都没有影响。所以在枚举 e 的时候,我们用一个堆或者是队列来维护权值最小的若干条线段就好了,时间复杂度 O(n*logn)。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #define LL long long #define M 200005 using namespace std; int n, l, s; LL a[M]; LL ans; int main() { freopen("travel.in","r",stdin); freopen("travel.out","w",stdout); scanf("%d%d%d", &n, &l, &s); for(int i = 1; i <= n; i++) { scanf("%I64d", &a[i]); } if(s!=1 && l==0) { //漏了 l=n-1,s不等于n 的情况 printf("-1\n"); return 0; } if(s-l == 1) { ans = s + n - 2; printf("%I64d\n", ans); for(int i = s-1; i >= 1; i--) printf("%d ", i); for(int i = s+1; i <= n; i++) printf("%d ", i); return 0; } if(s-l < 1) { int tmp = s - l; tmp -= s - 1; ans = s + n - 2 + 2 * tmp; printf("%I64d\n", ans); for(int i = s-1; i >= 1; i--) printf("%d ", i); printf("%d ", s+1); int t = s + 1; for(int i = 1; i <= tmp; i++) { printf("%d %d %d ", t+2, t+1, t+3); if(i != tmp) t += 3; } for(int i = t; i <= n; i++) printf("%d ", i); } ans = s-2*l-4+n; printf("%I64d\n", ans); for(int i = s; i >= s-l; i--) printf("%d ", i); printf("1 "); for(int i = 2; i < s-l; i++) printf("%d ", i); for(int i = s+1; i <= n; i++) printf("%d ", i); fclose(stdin); fclose(stdout); return 0; }
#include <stdio.h> #include <iostream> #include <algorithm> #include <memory.h> #include <string.h> #include <vector> using namespace std; typedef long long LL; typedef pair<int,int> mp; #define pb push_back const LL inf = 1ll<<50; const int maxn = 200005; int n,l,s,pos[maxn]; int x[maxn],ans1[maxn]; int y[maxn],ans2[maxn]; mp ord[maxn]; bool tag[maxn]; LL solve(int n,int l,int s,int x[],int ans[]) { int cnt=0,tot=0; if (l<s) { for (int i=s-1;i>s-l;i--) ans[++cnt]=i; for (int i=1;i<=s-l;i++) if (i!=s) ans[++cnt]=i; for (int i=s+1;i<=n;i++) ans[++cnt]=i; return (LL)x[n]-x[1]+x[s]-x[1]; } l-=s-1; if (l==n-s-1) { for (int i=s-1;i>=1;i--) ans[++cnt]=i; for (int i=n;i>s;i--) ans[++cnt]=i; return (LL)x[n]-x[1]+x[s]-x[1]+x[n]-x[s+1]; } for (int i=s+1;i<n-1;i++) ord[++tot]=mp(x[i+1]-x[i],i+1); sort(ord+1,ord+tot+1); for (int i=1;i<=tot;i++) pos[ord[i].second]=i; LL minv=inf,sum=0;int e,j; for (int i=1;i<=l;i++) sum+=ord[i].first; minv=sum*2;e=n;j=l; for (int i=n-1,p=l;i>=n-l;i--) { if (pos[i]<=p) sum-=ord[pos[i]].first; else sum-=ord[p--].first; while (p&&ord[p].second>=i) --p; if (sum*2+x[n]-x[i]<minv) { minv=sum*2+x[n]-x[i];e=i;j=p; } } memset(tag,false,sizeof tag); for (int i=s-1;i>=1;i--) ans[++cnt]=i; for (int i=s+2;i<e;i++) if (pos[i]<=j) tag[i]=true; for (int i=s+1;i<e;i++) if (!tag[i+1]) ans[++cnt]=i; else { int tmp=i+1;while (tag[tmp]) ++tmp; for (int j=tmp-1;j>i;j--) ans[++cnt]=j; ans[++cnt]=i;i=tmp-1; } for (int i=n;i>=e;i--) ans[++cnt]=i; return (LL)x[n]-x[1]+x[s]-x[1]+minv; } int main() { #ifndef ONLINE_JUDGE freopen("travel.in","r",stdin); freopen("travel.out","w",stdout); #endif scanf("%d %d %d",&n,&l,&s); for (int i=1;i<=n;i++) scanf("%d",&x[i]); for (int i=1;i<=n;i++) y[i]=-x[n-i+1]; if (s!=1&&l==0) {puts("-1");return 0;} if (s!=n&&l==n-1) {puts("-1");return 0;} LL cost1=solve(n,l,s,x,ans1); LL cost2=solve(n,n-1-l,n-s+1,y,ans2); if (cost1<cost2) { printf("%I64d\n",cost1); for (int j=1;j<n;j++) printf("%d ",ans1[j]); } else { printf("%I64d\n",cost2); for (int j=1;j<n;j++) printf("%d ",n-ans2[j]+1); } return 0; }