2021.06.12模拟总结
概述
端午假期的第一天集训,进行这次组队模拟。
学长:难度 \(\color{rgb(243, 156, 17)}{橙}\space\color{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^3\space =(a-b)\times(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+1\Rightarrow a^2+a\cdot(a+1)+(a+1)^2=p\)
得到:\(3a^2+3a+1=p\)。
由于这样得到的\(a\)较小,直接枚举\(a\)即可。
复杂度\(O(T\sqrt p)\)
或者,因为\(a\)的成立性有单调性,可以用二分来优化枚举过程。
复杂度\(O(T\log{\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\n",work() ? "YES" : "NO");
return 0;
}
T2 动态规划
题目名称言简意赅
题意
给定长度为\(n\)序列,分成\(k\)段,求每段相同数字对数的最小和。
解析
很考验思维的一道题,需要用决策单调性
优化。
首先,设\(dp[i][j]\)表示前\(i\)个数字分为\(j\)段的最小答案。
不难得到状态转移方程:\(dp[l][r]=dp[l][k]+calc(k+1,r)\)。
发现方程符合四边形不等式。
使用类似莫队
的方法优化这个过程,
具体详见代码。(复杂度为\(O(n\log 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(n\log 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;
}