Practice III
前言
已将今天想吐槽的话放在第一题的思路1和2中
(明天是七夕,点首不悲伤的曲子嘻嘻)
Vladik and Complicated Book - CodeForces - 811B
题意
给定一个无重复数字的序列,给定q个询问,每个询问给定 L,R,x,其中 L <= x <= R,问若将下标[L, R]中的数字排序,下标为x的数字是否发生了改变
思路1 —— 莫队算法 + 树状数组
个人对本题怨念很深 ╭(╯^╰)╮
以下是今天训练时的想法:
首先规模给了10^4,那么如果数据非常极端的话,最大规模达到 10^8,对子区间排序必超时,暴力应该也会超时
那么应该思考以下,什么情况下该数字不变,自然是它在区间中的相对下标等于它是第几小数
于是问题转化为,多次询问下求给定区间的第K小数,可以用主席树,还可以用划分树,等等别的数据结构和算法
个人选用了莫队算法 + 树状数组
以权值为下标建树(树状数组),动态将该数加入时则+1,反之-1,那么对于每个询问可以二分是第几小数是几,再查询是否与对应位置上的数相同,相同为Yes,不同为No
耗时46ms
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <iomanip>
#include <vector>
#include <cmath>
using namespace std;
const int N = 1e4 + 15;
const int inf = 0x3f3f3f3f;
typedef unsigned long long ll;
struct node{
int l, r, id, k;
};
int tree[N];
int block_size;
node que[N];
int a[N];
int ans[N];
bool cmp(const node& a, const node& b){
if(a.l/block_size == b.l/block_size){
return a.r < b.r;
}else{
return a.l/block_size < b.l/block_size;
}
}
inline void init(){
memset(tree, 0, sizeof(tree));
}
inline int lowbit(int idx) { return (idx & -idx); }
int getSum(int idx){
int sum;
for(sum = 0; idx > 0; idx -= lowbit(idx)){
sum += tree[idx];
}
return sum;
}
void update(int idx, int val){
while(idx < N){
tree[idx] += val;
idx += (idx & -idx);
}
}
int getKth(int n, int k){
int l = 1, r = n;
int ans = n;
while(l <= r){
int m = (l + r) >> 1;
if(getSum(m) >= k){
ans = m;
r = m - 1;
}else{
l = m + 1;
}
}
return ans;
}
void add(int idx){ update(a[idx], 1); }
void del(int idx){ update(a[idx], -1); }
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
init();
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
block_size = (int)sqrt(n);
for(int i = 1; i <= m; i++){
scanf("%d%d%d", &que[i].l, &que[i].r, &que[i].k);
que[i].id = i;
}
sort(que + 1, que + m + 1, cmp);
int l = 1, r = 0;
for(int i = 1; i <= m; i++){
while(que[i].l < l) add(--l);
while(l < que[i].l) del(l++);
while(que[i].r < r) del(r--);
while(r < que[i].r) add(++r);
ans[que[i].id] = (getKth(n, que[i].k - que[i].l + 1) == a[que[i].k]);
}
for(int i = 1; i <= m; i++){
puts(ans[i] ? "Yes" : "No");
}
}
}
思路2 —— 逆序对(暴力)
比赛完才知道,10^8 规模竟然不会超时,还挺快
只能说CF的服务器太好了
想打人 →_→
按照思路1中第k小数的思路,不使用算法和数据结构优化,直接找就好了
耗时 93ms
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int a[N];
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
while(m--){
int l, r, x;
int cnt = 0;
scanf("%d%d%d", &l, &r, &x);
for(int i = l; i <= r; i++){
if(a[i] <= a[x]) cnt++;
}
puts(x - l + 1 == cnt ? "Yes" : "No");
}
}
}
Vladik and Memorable Trip - CodeForces - 811C
题意
给定一个序列,要求选出若干个区间,他们的价值之和最大,要求选定的区间中出现的数必须全部在此区间中,区间的价值定义为区间中不同元素的异或和
思路
首先处理出如果要选定当前这个数,需要选定的区间是什么,再处理出这些区间的异或值
定义DP状态 dp[i] 表示选定第i个数时的最大价值,则DP方程为
if(i == dpl[a[i]]) dp[i] = max(dp[i + 1], dp[dpr[a[i]] + 1])
,表示当i这一位置是当前数字所在区间的左端点时,可以选择不选这个数字或者选这个区间,其中取最大值
if(i != dpl[a[i]]) dp[i] = dp[i + 1]
,表示若不为左端点,则只能不选
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <iomanip>
#include <vector>
using namespace std;
const int N = 5000 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int a[N], l[N], r[N], xorsum[N];
int dpl[N], dpr[N];
int dp[N];
bool used[N];
int main(){
int n;
while(~scanf("%d", &n)){
memset(l, 0x3f, sizeof(l));
memset(r, 0, sizeof(r));
memset(dpl, 0x3f, sizeof(dpl));
memset(dpr, 0, sizeof(dpr));
memset(xorsum, 0, sizeof(xorsum));
memset(dp, 0, sizeof(dp));
for(int i = 0; i < n; i++){
scanf("%d", &a[i]);
l[a[i]] = min(l[a[i]], i);
r[a[i]] = max(r[a[i]], i);
}
for(int i = 0; i < N; i++){
for(int j = l[i]; j <= r[i]; j++){
dpl[i] = min(dpl[i], l[a[j]]);
dpr[i] = max(dpr[i], r[a[j]]);
}
}
for(int i = 0; i < N; i++){
memset(used, 0, sizeof(used));
for(int j = dpl[i]; j <= dpr[i]; j++){
if(used[a[j]] == true) continue;
xorsum[i] ^= a[j];
used[a[j]] = true;
}
}
for(int i = n - 1; i >= 0; i--){
if(i != n - 1) dp[i] = dp[i + 1];
if(i == dpl[a[i]]) dp[i] = max(dp[i], dp[dpr[a[i]] + 1] + xorsum[a[i]]);
}
printf("%d\n", dp[0]);
}
}
Roman Digits - CodeForces - 998D
题意
若用I表示1,V表示5,X表示10,L表示50,问由这四个罗马数字组成n位的不同的数字由多少种可能性(不考虑顺序,比如IX表示11,而不是9)
思路
没思路看了一下题解,有点令人发指呀
打表找规律
前面的数字是无规律的,直接保存结果,后面的数字构成49为公差的等差数列,输出公式
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <set>
using namespace std;
const int N = 1e6 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
ll num[] = {0, 4, 10, 20, 35, 56, 83, 116, 155, 198, 244, 292};
int main(){
ios::sync_with_stdio(false);
ll n;
while(cin >> n){
if(n <= 11) cout << num[n] << endl;
else cout << num[11] + (n - 11) * 49 << endl;
}
}
Taxes - CodeForces - 735D
题意
给定一个数n,可将其拆分为 n = n1 + n2 + … + nk,每一个数的价值定义为它的最大公因子,问价值之和最小值(可以不拆)
思路
哥德巴赫猜想
这个数如果是质数,显然答案就是1
如果本身不是质数,那么如果是偶数,根据哥德巴赫猜想,任意偶数可由两个质数相加而成,因此答案可以为2
如果是奇数,根据偶数+奇数=奇数,那么如果 2+x=这个数,而x是质数,答案可以为2,因为2是质数中唯一的偶数
而如果不行,那么答案就得为3了
你可能会问哥德巴赫猜想不是还没有证明吗
然而题目数据是有范围的
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 15;
const int inf = 0x3f3f3f3f;
bool isPrime(int n){
for(int i = 2; i*i <= n; i++){
if(n%i == 0) return false;
}
return true;
}
int main(){
int n;
while(~scanf("%d", &n)){
if(isPrime(n)) puts("1");
else if(n%2 == 0) puts("2");
else if(isPrime(n - 2)) puts("2");
else puts("3");
}
}