0%

问题描述:

 对于任何正整数$x$,其约数的个数记作$g(x)$。如果某个正整数$x$满足:$g(x)>g(i)$ 其中$0<i<x$,则称x为反质数。现在给定一个数$N$你需要找出不大于$N$的最大反素数。

输入:

一个数$N$($1<=N<=2000000000$)。

输出:

不超过N的最大的反质数。

思路:

其实我们要求的就是$1-N$内约数个数最多的数(如果个数一样就是小的那个),然后我们可以利用一些神奇的性质用搜索解出答案。

首先反素数的质因子是不会超过10个的,并且质因子的指数是单调递减的。利用这两个性质缩小搜索范围,然后直接dfs枚举它的质因子就好了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include 
#define LL long long
using namespace std;

const int prime[]={2, 3, 5, 7, 11, 13, 17, 19, 23, 29};

LL n, maxsum, ans;

LL ksm(LL x, LL y)
{
LL sum=1;
while(y)
{
if(y&1) sum*=x;
y>>=1; x*=x;
}
return sum;
}

void dfs(int id, int cnt, int sum, LL num)
{
if(id==11)
{
if(sum>maxsum) ans=num, maxsum=sum;
if(sum==maxsum) if(numn) break;
dfs(id+1, i, sum*(i+1), num*ksm(prime[id], i));
}
}

int main()
{
scanf("%lld", &n);
dfs(0, 30, 1, 1);
printf("%lld\n", ans);
}

问题描述:

我们定义素数的距离为两个相邻素数之间存在合数的个数。给你一个区间$[l, r]$你需要求出区间内距离最大和最小的两对素数

输入:

有多组数据,每组数据两个数l, r 其中l, r不超过2147483647,区间大小不超过1000000

输出:

如果不存在两个质数输出”There are no adjacent primes.” 否则输出 “x1,x2 are closest, y1,y2 are most distant.”其中x1,x2,y1,y2为两对距离最大和最小的素数

思路:

由于l, r都很大,所以不方便之间线性筛。但区间大小不超过1000000,所以我们要利用好这个条件。

由于区间较小,我们可以利用类似于筛的方法筛掉区间的合数。我们可以先预处理出$2^{16}$内的素数,对于每一个区间我们通过枚举素数$prime[i]$,把区间内为$prime[i]$的倍数标记。枚举了所有素数后,区间内没有标记的数就是素数了。然后$O(n)$扫一遍计算出答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define MAXN 2000005
#define LL long long
using namespace std;

int l, r, tot, v[MAXN], prime[MAXN];
bool vis[1000005];

void getprime(int n)
{
for(int i=2; i<=n; i++)
{
if(!v[i]) prime[++tot]=i, v[i]=i;
for(int j=1; j<=tot; j++)
{
if(prime[j]>v[i] || prime[j]>n/i) break;
v[i*prime[j]]=prime[j];
}
}
}

int main()
{
getprime(MAXN-5);
while(scanf("%d%d", &l, &r)!=EOF)
{
if(l==1) l++;
int ans1, ans2, ans3, ans4, last=-1, maxn=-1, minn=1e9;
memset(vis, 0, sizeof(vis));
for(int j=1; j<=tot; j++)
{
int x=((LL)l+prime[j]-1)/prime[j], y=r/prime[j];
for(int i=max(2, x); i<=y; i++) vis[prime[j]*i-l]=1;
}

for(int i=0; i<=r-l; i++)
{
if(vis[i]) continue;
if(last==-1) {last=i; continue;}
if(i-last>maxn)
{
maxn=i-last;
ans1=last+l; ans2=i+l;
}
if(i-last<minn)
{
minn=i-last;
ans3=last+l; ans4=i+l;
}
last=i;
}
if(maxn==-1) printf("There are no adjacent primes.\n");
else printf("%d,%d are closest, %d,%d are most distant.\n", ans3, ans4, ans1, ans2);
}
}

问题描述:

一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小G给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小G不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小G对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的P次方,而一个排版的不协调度为所有行不协调度的总和。

小G最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小)。

输入:

输入文件中的第一行为一个整数T,表示诗的数量。

接下来为T首诗,这里一首诗即为一组测试数据。每组测试数据中的第一行为三个由空格分隔的正整数N,L,P,其中:N表示这首诗句子的数目,L表示这首诗的行标准长度,P的含义见问题描述。

从第二行开始,每行为一个句子,句子由英文字母、数字、标点符号等符号组成。

输出:

于每组测试数据,若最小的不协调度不超过10^18,则第一行为一个数,表示不协调度。接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。

如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。若最小的不协调度超过10^18,则输出“Too hard to arrange”(不含引号)。每组测试数据结束后输出“——————————”、。

思路:

这是一个DP应该不难想到,DP的方程也很简单:

然后我们需要将它优化,斜率优化能过6,7两个点,但后面的就比较难了。我们可以利用四边形不等式证明出这个DP是有决策单调性的(证明与p次方有关,利用什么求导搞。。反正我不懂)。然后就可以利用决策单调性来优化DP啦。

决策单调性是指:设$p[i]$为$f[i]$的最优决策,那么对于所有$j>i$都有$p[j]>=p[i]$。我们利用这个性质,可以利用单调队列+二分,在$O(nlogn)$内完成DP。

单调队列中的元素包含3个值:$p, l, r$,表示在区间$[l, r]$中的最优决策都是$p$。首先是把$(0, 0, n)$入队,然后对于每一个i: 1. 先检查队头元素的$[l, r]$是否包含了i,如果不包含就直接出队 2. 利用队头元素的最优决策$p$来更新$f[i]$ 3. 如果决策i比队尾的决策更优,那么我们就要插入决策i。于是我们就要找出区间$[l, r]$其中的最优决策都是i,首先$r$是为n(因为要靠后面决策来更新),$l$要利用二分找到。首先要清理队尾元素,也就是队尾元素整个区间$[l, r]$的决策i都更优,那么这个就可以出队。否则就在队尾元素区间$[l, r]$内二分,找到$l$的位置将决策i入队,并且更新队尾的$r$。

然后决策单调性的优化就是这样啦,自己理解了蛮久的。。。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long double
#define INF 1e18
#define MAXN 100005
using namespace std;

int T, n, l, p, head, tail;
LL sum[MAXN], f[MAXN];
char str[MAXN];

struct Node {int p, l, r;} q[MAXN];

LL cal(int j, int i)
{
LL num=1, x=sum[i]-sum[j]+i-j-1-l;
for(int i=1; i<=p; i++) num*=abs(x);
return f[j]+num;
}

int find(int l, int r, int j, int i)
{
while(l<=r)
{
int mid=(l+r)>>1;
if(cal(j, mid)<cal(i, mid)) l=mid+1;
else r=mid-1;
}
return l;
}

int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &l, &p);
for(int i=1; i<=n; i++)
{
scanf("%s", str);
int len=strlen(str);
sum[i]=sum[i-1]+len;
}

head=1, tail=0;
q[++tail]=(Node){0, 0, n};
for(int i=1; i<=n; i++)
{
if(head<=tail && i>q[head].r) head++;
f[i]=cal(q[head].p, i);
if(head>tail || cal(i, n)<=cal(q[tail].p, n))
{
while(head<=tail && cal(i, q[tail].l)<=cal(q[tail].p, q[tail].l)) tail--;
if(head>tail) q[++tail]=(Node){i, i, n};
else
{
int pos=find(q[tail].l, q[tail].r, q[tail].p, i);
q[tail].r=pos-1;
q[++tail]=(Node){i, pos, n};
}
}
}

if(f[n]>INF) printf("Too hard to arrange\n");
else printf("%lld\n", (long long)f[n]);
printf("--------------------\n");
}
}

问题描述:

机器上有N个需要处理的任务,它们构成了一个序列。这些任务被标号为1到N,因此序列的排列为1,2,3…N。这N个任务被分成若干批,每批包含相邻的若干任务。

从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和。注意,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。

输入:

第一行两个整数,N,S。 接下来N行每行两个整数,Ti,Fi。

输出:

一个整数,为所求的答案。

思路:

首先$O(n^3)$的DP不难想到,这里就不多说了。然后$O(n^2)$的做法比较巧妙,我们可以提前计算出这个启动时间对后面状态造成对影响,然后用前缀和预处理一下$sumc$,$sumt$。所以状态转移方程为:

如果我们将上面对式子进行变形,就可以发现它是可以使用斜率优化的: 其中$f[j]$是纵坐标,$sumc[j]$是横坐标,斜率是$s+sumt[i]$。然后我们就要单调队列维护这些点构成的凸壳,使得两点之间的斜率是递增的。对于$i$的最优决策就是就是在凸壳上的某个点$j$,它左边的斜率小于$s+sumt[i]$,右边的斜率大于$s+sumt[i]$,然后我们就可以用这个j来更新$f[i]$。

最后有一点要注意的就是题目中的$T$能是负数,所以我们不能够像一般的斜率优化根据斜率的单调性pop掉队头元素。而是要改为二分来求出最优的决策点j。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <bits/stdc++.h>
#define LL long long
#define MAXN 300005
using namespace std;

int n, s, head, tail;
LL sumc[MAXN], sumt[MAXN], q[MAXN], f[MAXN];

int find(int k)
{
int l=tail, r=head, ans;
while(l<r)
{
int mid=(l+r)>>1;
if(f[q[mid+1]]-f[q[mid]]<=k*(sumc[q[mid+1]]-sumc[q[mid]])) l=mid+1;
else r=mid;
}
return q[l];
}

int main()
{
scanf("%d%d", &n, &s);
for(int i=1; i<=n; i++)
{
int t, c;
scanf("%d%d", &t, &c);
sumt[i]=sumt[i-1]+t;
sumc[i]=sumc[i-1]+c;
}
head=tail=1; q[head]=0;
for(int i=1; i<=n; i++)
{
int j=find(s+sumt[i]);
f[i]=f[j]-(s+sumt[i])*sumc[j]+sumt[i]*sumc[i]+s*sumc[n];
while(tail<head && (f[q[head]]-f[q[head-1]])*(sumc[i]-sumc[q[head]])>=(f[i]-f[q[head]])*(sumc[q[head]]-sumc[q[head-1]])) head--;
q[++head]=i;
}
printf("%lld\n", f[n]);
}

7月13日 Codeforces Round #496 题目链接:http://codeforces.com/contest/1005

A - Tanya and Stairways & B - Delete from the Left

过水,不多说。。。

C - Summarize to the Power of Two

思路: $O(nlogn^2)$的比较暴力的做法,估计不是正解,水过去的QWQ

就是对于一个数$a[i]$,我们可以枚举所有的2的次幂,找到数组$a$在中是否有另一个数$a[j]$,使得它们和为2的次幂,这个可以用map维护。但是要注意一个细节就是a[i]本身是2的次幂,这个要特判一下。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <bits/stdc++.h>
#define MAXN 120005
using namespace std;

int n, a[MAXN], ans;
map<int, int> mmp;

int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
mmp[a[i]]++;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=30; j++)
if(mmp.count((1<<j)-a[i]) && ((1<<j)!=a[i]*2 || mmp[a[i]]>1))
{ans++; break;}
printf("%d\n", n-ans);
}

D - D - Polycarp and Div 3

思路: 首先有一个引理:就是如果有一串长度大于3的数字它们和能被3整除,那么一定有一个连续字串的和也能被3整除。

这个比较显然,证明就不多说了。我们进行DP,根据上面的引理,每个$f[i]$会只从它前面的$f[i-1],f[i-2],f[i-3]$转移而来,所以就直接$O(n)$扫一遍就好了。(DP如何转移就看代码好了,比较简单)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <bits/stdc++.h>
#define MAXN 200005
using namespace std;

int n, cnt=1, ans, num[MAXN], f[MAXN][2];

int main()
{
while(scanf("%1d", &num[cnt])!=EOF) cnt++;
for(int i=1; i<cnt; i++) num[i]+=num[i-1];
for(int i=1; i<cnt; i++)
for(int j=max(i-3, 0); j<i; j++)
{
if((num[i]-num[j])%3==0) f[i][1]=max(f[i][1], f[j][1]+1);
else f[i][1]=max(f[i][1], f[j][1]);
}
printf("%d\n", f[cnt-1][1]);
}

E1 - Median on Segments (Permutations Edition)

思路: 这题我的思路和题解的也不太一样。(其实是当时想一口气做出两个来,结果发现这个思路E2有BUG)

首先我们对于序列中数的大小是不关心的,只关系它与k的关系。所以可以把比k小的看成-1,比k大的看成1,然后维护前缀和,记为num[i]。我们同样记录k的位置pos,那么答案就是在pos两边有多少对i,j使得$num[i]=num[j]$或$num[i]=num[j]+1$。所以我们cnt数组记录在pos前$num[i]$的数量,对于pos后的$num[i]$就将$cnt[num[i]]$和$cnt[num[i]-1]$加入答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
#define MAXN 200005
#define LL long long
using namespace std;

int n, k, pos=MAXN, a[MAXN], num[MAXN], cnt[MAXN*2];
LL ans;

int main()
{
scanf("%d%d", &n, &k);
cnt[n]=1;
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
if(a[i]==k) pos=i;
if(a[i]<k) num[i]=-1;
if(a[i]==k) num[i]=0;
if(a[i]>k) num[i]=1;
num[i]+=num[i-1];
if(i<pos) cnt[num[i]+n]++;
}
for(int i=pos; i<=n; i++) ans+=cnt[n+num[i]], ans+=cnt[n+num[i]-1];
printf("%lld\n", ans);
}

E2 - Median on Segments (General Case Edition)

思路: 这道题自然是没有想到的QWQ…..膜了下题解

由于直接求中位数为m的序列是不好求的,所以我们可以变一下,求中位数大于m的序列。那么答案就是做一个容斥,为中位数大于m的序列—中位数大于m+1的序列。

那么求中位数大于m的序列的做法和上一题我的做法有些相似。$num$是前缀和,$cnt$数组记录数量,由于长度为偶数时中位数是中间较小的那个,所以操作的顺序是不一样的。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <bits/stdc++.h>
#define MAXN 200005
#define LL long long
using namespace std;

int n, m, a[MAXN];
LL num, cnt[MAXN*2];

LL solve(int m)
{
LL ans=0, add=0;
memset(cnt, 0, sizeof(cnt));
num=n; cnt[num]=1;
for(int i=1; i<=n; i++)
{
if(a[i]<m) num--, add-=cnt[num];
else add+=cnt[num], num++;
ans+=add;
cnt[num]++;
}
return ans;
}

int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
printf("%lld\n", solve(m)-solve(m+1));
}

F - Berland and the Shortest Paths

思路: 用BFS一遍处理出每个点深度,用个vector记录每个点连向父亲的边(可能有多个父亲)。因为每个点vector中存的边可以随便取,所以用dfs乱搞一下计算出答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#define MAXN 200005
using namespace std;

int n, m, k, ans, cnt=1, head[MAXN], dep[MAXN], temp[MAXN];
bool vis[MAXN];

struct Edge {int next, to;} edge[MAXN*2];

vector<int> pre[MAXN], path[MAXN];
queue<int> q;

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}

void bfs()
{
memset(dep, 0x3f, sizeof(dep));
q.push(1); dep[1]=0;
while(!q.empty())
{
int x=q.front(); q.pop();
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(dep[to]>dep[x]+1)
{
dep[to]=dep[x]+1;
q.push(to);
}
if(dep[to]==dep[x]+1) pre[to].push_back(i);
}
}
}

void dfs(int x)
{
if(x==n+1)
{
for(int i=0; i<m; i++) path[ans].push_back(temp[i]);
ans++;
return;
}
for(int i=0; i<pre[x].size(); i++)
{
temp[pre[x][i]/2-1]=1;
dfs(x+1);
if(ans==k) return;
temp[pre[x][i]/2-1]=0;
}
}

int main()
{
scanf("%d%d%d", &n, &m, &k);
for(int i=1; i<=m; i++)
{
int x, y;
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
bfs();
dfs(2);
printf("%d\n", ans);
for(int i=0; i<ans; i++)
{
for(int j=0; j<m; j++) printf("%d", path[i][j]);
printf("\n");
}
}