概述
端午假期的第一天集训,进行这次组队模拟。
学长:难度 (color{rgb(243, 156, 17)}{橙}spacecolor{rgb(52, 152, 219)}{蓝space蓝})
T1 立方数(cubicp)
题意
给定质数(p),求是否满足(exists a,b),使得(p=a^3-b^3)。
解析
把(p=a^3-b^3)转化一下,得到:
(p=a^3-b^3space =(a-b) imes(a^2+ab+b^2))
其中,因为(p)是质数,那么分解出来的两个因数(a-b)和(a^2+ab+b^2)一个为(1),一个为(p)。
显然有(|a-b|=1),(a^2+ab+b^2=p)。
所以原式转化为:(b=a+1Rightarrow a^2+acdot(a+1)+(a+1)^2=p)
得到:(3a^2+3a+1=p)。
由于这样得到的(a)较小,直接枚举(a)即可。
复杂度(O(Tsqrt p))
或者,因为(a)的成立性有单调性,可以用二分来优化枚举过程。
复杂度(O(Tlog{sqrt p}))
代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 1e2;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0 ; char ch = ' ' , c = getchar();
while(!(c >= '0' && c <= '9'))ch = c, c = getchar();
while(c >= '0' && c <= '9')ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
return ch == '-' ? -ret : ret;
}
ll check(ll x){return 3*x*x + 3*x + 1;}
inline bool work(){
ll p = read();
ll l = 1 , r = 1e9;
while(l < r){
int mid = (l + r + 1) >> 1;
if(check(mid) <= p) l = mid;
else r = mid - 1;
}
return check(l) == p;
}
signed main(){
// fo("cubicp");
int T = read();
while(T--)
printf("%s
",work() ? "YES" : "NO");
return 0;
}
T2 动态规划
题目名称言简意赅
题意
给定长度为(n)序列,分成(k)段,求每段相同数字对数的最小和。
解析
很考验思维的一道题,需要用决策单调性
优化。
首先,设(dp[i][j])表示前(i)个数字分为(j)段的最小答案。
不难得到状态转移方程:(dp[l][r]=dp[l][k]+calc(k+1,r))。
发现方程符合四边形不等式。
使用类似莫队
的方法优化这个过程,
具体详见代码。(复杂度为(O(nlog n k)))
一些问题
- 关于
add
和del
的先加后加问题:- Add一个数字(x),对答案的贡献(=)序列内已有的(x)的个数(加入的数和其余数分别组对),所以Add是
ans += buc[a[x]]++;
- Del一个数字(x),对答案的贡献(=)序列内已有的(x)的个数-1(删除的数和其余数分别组队),所以Del是
ans -= --buc[a[x]];
- Add一个数字(x),对答案的贡献(=)序列内已有的(x)的个数(加入的数和其余数分别组对),所以Add是
- 关于
move
的先加后加问题:- 区间扩展一个,与上
Add
同 - 区间减小一个,与上
Del
同
- 区间扩展一个,与上
- 另外:区间移动时倒序比正序略快(不知为何的玄学优化)
代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 1e5+5 , M = 22;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0 ; char ch = ' ' , c = getchar();
while(!(c >= '0' && c <= '9'))ch = c, c = getchar();
while(c >= '0' && c <= '9')ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
return ch == '-' ? -ret : ret;
}
int n,k;
int a[N],buc[N];
int pl,pr,cur;
ll ans,dp[N][M];
inline void add(int x){ans += buc[a[x]]++;}
inline void del(int x){ans -= --buc[a[x]];}
inline ll calc(int l,int r){
while(pl > l)add(--pl);
while(pr < r)add(++pr);
while(pl < l)del(pl++);
while(pr > r)del(pr--);
return ans;
}
void dfs(int l,int r,int pl,int pr){
if(l > r)return ;
int mid = (l + r) >> 1,p;
dp[mid][cur] = 1LL*INF*INF;
for(int i = min(mid-1,pr) ; i >= pl ; i --){
ll t = dp[i][cur-1] + calc(i+1,mid);
if(t < dp[mid][cur])
dp[mid][cur] = t,p = i;
}
dfs(l,mid-1,pl,p);
dfs(mid+1,r,p,pr);
}
signed main(){
n = read() , k = read();
pl = 1 , pr = 0;
for(int i = 1 ; i <= n ; i ++)
a[i] = read();
for(int i = 1 ; i <= n ; i ++)
dp[i][1] = calc(1,i);
for(int i = 2 ; i <= k ; i ++)
cur = i , dfs(1,n,0,n);
printf("%lld",dp[n][k]);
return 0;
}
3.游戏
题意
有(n)个数字和(T)次给定询问,每次询问给定([l,r])的最小值(x),求第几次操作是矛盾的。
解析
对于(T)次询问,可以先进行按(x)从大到小排序。
对于每一些(x)相同的区间,它们会有交集和并集。维护这个交集和并集。
- 对于交集:是能确定(x)在的最小的区间,若此段已锁定是更大的值而无处安放,则矛盾。
- 对于并集:是(x)所在最大的区间,也就是上面提到的
锁定
。
可以维护一个线段树,每个叶节点均为1.若锁定则改为0.
查询时,找到最小区间,判断区间和是否为0即可。
修改时,找到最大区间,修改为0即可。
复杂度:(O(nlog n log T))
代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 1e6;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0 ; char ch = ' ' , c = getchar();
while(!(c >= '0' && c <= '9'))ch = c, c = getchar();
while(c >= '0' && c <= '9')ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
return ch == '-' ? -ret : ret;
}
int n,T;
struct node{int id,l,r,x;}a[N]; inline bool operator < (node a,node b){return a.x > b.x;}
int tree[N<<2];
void modify(int k,int l,int r,int x,int y){
if(!tree[k])return ;
if(x <= l && r <= y){tree[k] = 0;return;}
int mid = (l + r) >> 1;
if(x <= mid) modify(k<<1,l,mid,x,y);
if(y >= mid + 1) modify(k<<1|1,mid+1,r,x,y);
tree[k] = tree[k<<1] + tree[k<<1|1];
}
int query(int k,int l,int r,int x,int y){
if(!tree[k])return 0;
if(x <= l && r <= y)return tree[k];
int mid = (l + r) >> 1 , ret = 0;
if(x <= mid) ret += query(k<<1,l,mid,x,y);
if(y >= mid + 1) ret += query(k<<1|1,mid+1,r,x,y);
return ret;
}
void build(int k,int l,int r){
if(l == r) {tree[k] = 1;return;}
int mid = (l + r) >> 1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
tree[k] = tree[k<<1] + tree[k<<1|1];
}
inline bool check(int x){
build(1,1,n);
int now = INF , l = INF , r = -INF , cl = -INF , cr = INF;
for(int i = 1 ; i <= T ; i ++)
if(a[i].id <= x){
node f = a[i];
if(a[i].x == now){
l = min(l,a[i].l);
r = max(r,a[i].r);
cl = max(cl,a[i].l);
cr = min(cr,a[i].r);
}
else{
if(query(1,1,n,cl,cr))
modify(1,1,n,l,r);
else return 0;
now = a[i].x;
cl = l = a[i].l , cr = r = a[i].r;
}
}
return query(1,1,n,cl,cr);
}
signed main(){
// fo("number");
n = read() , T = read();
for(int i = 1 ; i <= T ; i ++)
a[i].l = read() , a[i].r = read() , a[i].x = read() , a[i].id = i;
sort(a+1,a+n+1);
int l = 1 , r = T;
while(l < r){
int mid = (l + r + 1)>>1;
int tmp;
if(tmp = check(mid)) l = mid;
else r = mid-1;
}
printf("%d",l+1);
return 0;
}