CCPC2018-湖南全国邀请赛(感谢湘潭大学)
A - Easy h-index
题意:作者的h索引,就是最大的h,使得他最少有h篇文章被引用了h次,给出a1,a2,...,an,表示被引用i次的文章有ai个
题解:从后往前做后缀和,当sum[i] >= i时,i即为所求h
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll sum = 0, ans;
int n, a[200005];
bool cmp(int a, int b){
return a > b;
}
int main() {
while (~scanf("%d", &n)){
for (int i = 0;i <= n;i++){
scanf("%d",&a[i]);
}
sum = 0, ans = 0;
for (int i = n;i >= 0;i--){
sum += a[i];
if (sum >= i){
ans = i;
break;
}
}
printf("%lld\n", ans);
}
return 0;
}
B - Higher h-index
题意:作者的h索引,就是最大的h,使得他最少有h篇文章被引用了h次,给出n和a,表示一共工作n小时,每篇文章工作x小时那么这篇文章会被引用a*x次,后面发表的文章会引用前面的文章,问作者的h
题解:分析可得每篇文章工作1小时(可反证法证明)答案最优,这时文章得应用次数分别为1,2,3……n+a,由第一题直每种引用都有一篇文章,sum[i] = n + a - i, 要使得sum[i]>=i, 即n+a-i>=i,化简得i<=(n+a)/2,所以h为最大得i,为(n+a)/2
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, a;
while (~scanf("%d%d", &n, &a)){
cout << (n + a) / 2 << endl;
}
return 0;
}
C - Just h-index
题意:作者的h索引,就是最大的h,使得他最少有h篇文章被引用了h次,给出每篇文章得引用次数a[i],求给出某一段[l, r]的文章的h
题解1:莫队+树状数组+二分
根据第一题,我们知道要想算出h,必须知道[l, r]区间上某个k,sum[k, r] >= k, k可以通过二分枚举,求和用树状数组维护,离线算出每个[l, r]用莫队
题解2:主席树+二分
主席树可以直接求出[l, r] 区间上某一段的和,直接二分枚举k,使得在[l,r]上sum[k] >= k
code1:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
using namespace std;
typedef long long int ll;
const int MAXN=100015;
int N,Q;
int a[MAXN];
struct query {
int l;
int r;
int id;
} q[MAXN];
int block[MAXN];
int blocksize;
bool cmp(query a,query b) {
if(block[a.l]==block[b.l])
return a.r<b.r;
return block[a.l]<block[b.l];
}
int ans[MAXN];
int tree[MAXN];
int tot=0;
int lowbit(int x) {
return x&-x;
}
void add(int x,int C) {
for(int i=x; i<=MAXN; i+=lowbit(i)) {
tree[i]+=C;
}
}
int sum(int x) {
int ans=0;
for(int i=x; i; i-=lowbit(i))
ans+=tree[i];
return ans;
}
bool judge(int x) {
if(tot-sum(x-1)-x>=0)
return true;
return false;
}
void solve() {
int l=1;
int r=1;
tot++;
add(a[1],1);
for(int i=0; i<Q; i++) {
while (q[i].r > r) {
r++;
add(a[r],1);
tot++;
}
while (q[i].r < r) {
add(a[r],-1);
r--;
tot--;
}
while (q[i].l > l) {
add(a[l],-1);
l++;
tot--;
}
while (q[i].l < l) {
l--;
add(a[l],1);
tot++;
}
int ll=1,rr=N;
int m;
while(ll<=rr) {
m=(ll+rr)/2;
if(judge(m)) {
ll=m+1;
} else
rr=m-1;
}
ans[q[i].id]=rr;
}
}
int main() {
while(~scanf("%d%d",&N,&Q)) {
tot=0;
memset(tree,0,sizeof(tree));
blocksize=sqrt(N);
for(int i=1; i<=N; i++) {
scanf("%d",&a[i]);
block[i]=(i-1)/blocksize+1;
}
for(int i=0; i<Q; i++) {
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q,q+Q,cmp);
solve();
for(int i=0; i<Q; i++) {
printf("%d\n",ans[i]);
}
}
return 0;
}
code2:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
struct node{
int l, r, sum;
}T[maxn * 50];
int root[maxn], a[maxn];
int n, q, cnt;
void update(int l, int r, int &x, int y, int pos){
T[++cnt] = T[y];
T[cnt].sum++;
x = cnt;
if (l == r) return;
int mid = (l + r) / 2;
if (pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
else update(mid + 1, r, T[x].r, T[y].r, pos);
//T[x].sum += T[T[x].l].sum + T[T[x].r].sum;
}
int query(int l, int r, int x, int y, int k){
if (k <= l) return T[y].sum - T[x].sum;
int mid = (l + r) / 2;
if (k <= mid) return T[T[y].r].sum - T[T[x].r].sum + query(l, mid, T[x].l, T[y].l, k);
else return query(mid+1, r, T[x].r, T[y].r, k);
}
int judge(int x, int l, int r){
return query(1, n, root[l -1] , root[r], x);
}
int main()
{
while (~scanf("%d%d", &n, &q)){
memset(T, 0, sizeof(T));
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)
update(1, n, root[i], root[i - 1], a[i]);
for (int i = 0; i < q; i++){
int x, y;
scanf("%d%d", &x, &y);
//printf("%d\n", query(1, n, root[x-1], root[y]))
int ll=1, rr=n, ans = 1;
while (ll <= rr){
int mid = (ll + rr) / 2;
if (judge(mid, x, y) >= mid){
ans = mid;
ll = mid + 1;
}else{
rr = mid - 1;
}
}
printf("%d\n", ans);
}
}
return 0;
}
F - Sorting
题意:给出两个数比较大小的规则,排序
题解:将给定的比较大小的规则化简,写个cmp,排序
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int maxn = 1e3+2;
struct node {
ll a, b, c;
int id;
} a[maxn];
bool cmp(node A, node B) {
if (A.a*B.c + A.b * B.c == B.a * A.c + B.b * A.c) return A.id < B.id;
return (A.a*B.c + A.b * B.c) < (B.a * A.c + B.b * A.c);
}
int n;
int main() {
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; i++) {
scanf("%llu%llu%llu", &a[i].a, &a[i].b, &a[i].c);
a[i].id = i;
}
sort(a +1 , a +1 + n, cmp);
for (int i = 1; i < n; i++) {
printf("%d ", a[i].id);
}
printf("%d\n", a[n].id);
}
return 0;
}
G - String Transformation
题意:给出A串,B串,都只含有a,b,c,问是否将A串变到B串通过{aa, bb, abab}
题解:首先变换没有c,所以c的个数不同肯定不行,然后通过c将A,B划分成若干子串,对于每个子串,由样例知ab可以换成ba,也就是说顺序可以换,然后变换都是偶数个,所以如果a,b在这个子串中的个数奇偶性不相同则不行,否则可以完成变换
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+2;
char s[maxn], t[maxn];
int sc, tc;
int ls, lt, numsa, numta,numsb, numtb, flag;
vector <int> segsa, segta, segsb, segtb;
int main(){
while (cin >> s >> t){
segsa.clear();
segta.clear();
segsb.clear();
segtb.clear();
ls = strlen(s);
lt = strlen(t);
sc = 0, tc = 0;
for (int i = 0; i < ls; i++){
if (s[i] == 'c') sc++;
}
for (int i = 0; i < lt; i++){
if (t[i] == 'c') tc++;
}
if (sc != tc) {
puts("No");
continue;
}
numsa = 0;flag = 1;
numsb = 0;
for (int i = 0; i < ls; i++){
if (s[i] == 'c'){
segsa.push_back(numsa);
segsb.push_back(numsb);
numsa = 0;
numsb = 0;
}
else if (s[i] == 'a') numsa++;
else if (s[i] == 'b') numsb++;
}
segsa.push_back(numsa);
segsb.push_back(numsb);
numta = 0; numtb = 0;
for (int i = 0; i < lt; i++){
if (t[i] == 'c'){
segta.push_back(numta);
segtb.push_back(numtb);
numta = 0;
numtb = 0;
}
else if (t[i] == 'a') numta++;
else if (t[i] == 'b') numtb++;
}
segta.push_back(numta);
segtb.push_back(numtb);
for (int i = 0; i < segsa.size(); i++){
if (abs(segsa[i] - segta[i]) % 2 == 1) flag = 0;
if (abs(segsb[i] - segtb[i]) % 2 == 1) flag = 0;
//cout << segsa[i] << " " << segta[i] << endl;
//cout << segsb[i] << " " << segtb[i] << endl;
// cout << segs[i] << " " << segt[i] << endl;
}
if (flag) puts("Yes");
else puts("No");
}
return 0;
}
I - Longest Increasing Subsequence
题意:给一串数字,其中有0,求Σi*f(i),f(i)是将原串中的0都换成i的最长上升子序列
题解:分析可知,将0换成i,最多只能将串的长度增加1,即将i插入最长上升子序列的某两个数中间,并且这两个数在原先序列中中间必须有0存在,如果某个零前面的最长上升子序列加上后面的最长上升子序列长度等于原先最长上升子序列,并且这个0换成某个数能插入的话,长度就会加1.
记录a[i],以 i 结束的最长上升子序列长度
记录b[i].以 i 开始的最长上升子序列长度
总最长上升子序列为len
c[i]为原序列
处理的时候就从后往前对于每一位i,他前面LIS=a[i],如果后面有个LIS = len - a[i],即某个j,b[j] = len-a[i],j > i,那么[c[i]+1,c[j]-1]中间的数队总答案的贡献都是len+1,(可以用差分序列,降低复杂度到O(1),线段树等数据结构复杂度都是logn量级),如果我们能快速找到这个j,问题就解决了,用f[i],记录后面出现过的b[j] = len - a[i] 的最大的j
#include <bits/stdc++.h>
#define FOR(i,s,t) for(int i=(s);i<=(t);i++)
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int a[maxn], b[maxn], c[maxn], f[maxn];
int n, m, len;
ll d[maxn];
int main()
{
while (~scanf("%d", &n)){
FOR(i, 0, n + 1) a[i] = b[i] = c[i] = d[i] = 0;
FOR(i, 1, n) scanf("%d",&c[i]);
FOR(i, 0, n + 1) f[i]= INF;
for (int i = 1; i <= n; i++){
if (!c[i]) continue;
int j = lower_bound(f+1, f+1+n, c[i]) - f ;
a[i]=j;f[j]=c[i];
}
FOR(i, 0, n+1) f[i] = 0;
for (int i = n; i>0;i--){
if(!c[i]) continue;
int j = lower_bound(f+1,f+1+n,-c[i]) - f;
b[i]=j;f[j]=-c[i];
}
len = 1;
while (f[len]) len++;
len--;
// cout << len << endl;
FOR(i, 0, n + 1) f[i] = 0;
for (int i = n; i >= 1; i--){
int j = i;
while (c[j]){
int t = len - a[j];
if (f[t] > c[j] + 1){
d[c[j]+1]++;d[f[t]]--;
}
j--;
}
f[0] = n + 1;
for (int k = i; k > j; k--) f[b[k]] = max(f[b[k]], c[k]);
if (f[len] > 1 && j){
d[1]++;d[f[len]]--;
}
i = j;
}
//cout << len << endl;
ll ans = 0;
for (int i = 1; i <= n;i++){
d[i] += d[i-1];
ans += (ll)len * i;
if (d[i] > 0) ans += i;
}
printf("%lld\n", ans);
}
return 0;
}
J - Vertex Cover
题意:给出一些点,每个点有重量,i号点的重量为 2^i ,定义一个点覆盖着一条边只需要覆盖其中一个点,给出一个完全图,已知用一些点覆盖边的最优方案(重量最小),问原先的边有多少种情况
题解:重量大的点假设叫高位,重量小的叫低位,因为题目是以二进制形式给出的,1代表选的点,0代表没选
对于某个1,可以连向高位的0(必连),不能连高位的1(这样不是以最优方案选,若有这条边,则会选去重量小的,即选低位1,不会选高位1),可以连低位的0或1(选连),假设高为有p个0,低位有q位,答案位(2^p-1)*(2^q)
code:我的代码有问题,先po一个人家的
#include<stdio.h>
#include<string.h>
#define LL long long
#define mod 1000000007
char str[100005];
LL er[100005] = {1};
int main(void) {
LL now, ans;
int n, i, len, m, sum;
for(i=1; i<=100002; i++)
er[i] = er[i-1]*2%mod;
while(scanf("%d%s", &n, str+1)!=EOF) {
m = strlen(str+1);
len = n-m;
ans = 1, sum = 0;
for(i=1; i<=m; i++) {
now = 1;
if(str[i]=='1') {
now = er[len];
now = (now-er[sum]+mod)%mod;
sum++;
} else
now = er[sum];
len++;
ans = (ans*now)%mod;
}
printf("%lld\n", ans);
}
return 0;
}
K - 2018
题意:给出a, b , c, d, x属于[a, b] , y属于[c, d],问有多少数x*y是2018的倍数
题解:容斥,素数分解发现2018=2*1009
x是2018倍数,y任意数
x只是2的倍数,y必是1009倍数
x只是1009倍数,y必是2的倍数
x既不是2的倍数也不是1009的倍数,y是2018的倍数
#include <iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
int main() {
ll a, b, c, d, x1, x2, x3, x4;
ll l1, l2, l1_2, l1_1009, l1_2018, l2_2, l2_1009, l2_2018;
while(cin >> a >> b >> c >> d) {
l1 = b - a + 1;
l2 = d - c + 1;
l1_2 = b / 2 - (a - 1) / 2;
l1_1009 = b / 1009 - (a - 1) / 1009;
l1_2018 = b / 2018 - (a - 1) / 2018;
l2_2 = d / 2 - (c - 1) / 2;
l2_1009 = d / 1009 - (c - 1) / 1009;
l2_2018 = d / 2018 - (c - 1) /2018;
x1 = l1_2018 * l2;
x2 = (l1_2 - l1_2018) * l2_1009;
x3 = (l1_1009 - l1_2018) * l2_2;
x4 = (l1-l1_2 - l1_1009 + l1_2018) * l2_2018;
printf("%lld\n",x1+x2+x3+x4);
}
return 0;
}