10月23日比赛
T1
看了一眼想到了暴力怎么写,但是看看数据范围,很明显就是给 \(O(n^2)\) 和 \(O(n^2logn)\) 的,那么就好很自然的想到枚举两个确定另外一个。
性质
1.只要求下标递增,那么直接枚举即可无需排序。
2.可以开桶统计这个数出现在了那些位置,然后排序,排完序二分位置,这是在vector中size-pos就是答案。
3.开桶需要离散化。
复杂度
\(O(n^2logn)\),100pts
没开 \(long\ long\) 挂了30pts
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[3077],ans;
vector<int>mp[3077];
map<int,int>xb;
int tot;
signed main() {
cin>>n;
for(int i = 1;i <= n;++ i) {
scanf("%lld",&a[i]);
if(!xb[a[i]]) xb[a[i]] = ++tot;
mp[xb[a[i]]].push_back(i);
}
for(int i = 1;i <= tot;++ i) {
sort(mp[i].begin(),mp[i].end());
}
for(int x = 1;x < n;++ x) {
for(int y = x+1;y <= n;++ y) {
int az = xb[__gcd(a[x],a[y])];
if(!mp[az].size()) continue;
else {
int pos = upper_bound(mp[az].begin(),mp[az].end(),y)-mp[az].begin();
ans += mp[az].size()-pos;
}
}
}
cout<<ans;
return 0;
}
T2
我自己清楚自己dp练的少,所以第一眼没看出来怎么写。
所以乱搞了一波。
设 \(f[i]\) 表示 \(1-i\) 的最长子序列的长度,同时用一个 \(set\) 去记录这个最长的长度的子序列的最后一个是多少。
我用这个 \(set\) 的目的是为了转移的时候可以将所有的合法状态都判断一次,这样就可以保证转移不漏。
然后这个dp,无论是状态设计还是转移都特别麻烦。
但是你不能说他不对,因为他拿到了80pts
正解
设 \(f[i]\) 表示以 \(i\) 为结尾的最长子序列的长度。
很显然这个状态设计就是限制了我最后一个必须选择 \(a_i\),
既这个子序列的最后一个必定是 \(a_i\),那么这个我们就可以方便的比较当前的和上一个,就不需要另开一个 \(set\) 去存上一个数是多少了,可以说是状态简单,转移简单。
100pts
状态设计确实很重要。
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
int n,m,a[1011];
struct op{
int val;
set<int>lt;
}f[1011];
int ans;
bool chck(int lim) {
for(int i = 1;i <= n;++ i) f[i].val=1,f[i].lt.clear();
f[1].val=1,f[1].lt.insert(a[1]);
for(int i = 2;i <= n;++ i) {
int res = 0;
for(int j = 1;j < i;++ j) {
if(abs(a[i]-a[j]) <= lim)
f[i].val = max(f[i].val,f[j].val+1);
}
if(f[i].val>=m) return 1;
}
return 0;
}
signed main() {
cin>>n>>m;
for(int i = 1;i <= n;++ i) {
scanf("%lld",&a[i]);
}
if(chck(0)) {
cout<<0;
return 0;
}
ll l = 1,r = 2e9;
while(l <= r) {
ll mid = ((l+r)>>1);
if(chck(mid)) r = mid-1,ans = mid;
else l = mid+1;
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号