目录
- [J 思维 完全平方数](#J 思维 完全平方数)
- [K 树形DP](#K 树形DP)
- [C 二分答案 贪心 转化问题](#C 二分答案 贪心 转化问题)
- [E 前缀和 贪心 查找](#E 前缀和 贪心 查找)
J 思维 完全平方数
最后得到的平方数是什么样的?
- 对于一般因数p, n o w = x ⋅ p now=x\cdot p now=x⋅p,难于确定+p后 n o w + p = ( x + 1 ) ⋅ p now+p=(x+1)\cdot p now+p=(x+1)⋅p, x + 1 x+1 x+1有什么因数,再找的话需要 O ( x ) O(\sqrt{x}) O(x ),大概是1e6的规模
- 而且也难以确定选哪个因数进行操作才能使操作数<=100
注意 2 2 k 2^{2k} 22k也是一种平方数,而且 2 2 k − 1 + 2 2 k − 1 = 2 2 k 2^{2k-1}+2^{2k-1}=2^{2k} 22k−1+22k−1=22k,加因数很容易得到
cpp
int lowbit(int x){return x&(-x);}
void solve(){
int n;cin>>n;
/*
int tp=n;
vector<int>p;
forr(i,2,sqrt(n)){
if(tp%i==0){
p.push_back(i);
while (tp%i==0)tp/=i;
}
if(tp==1)break;
}
if(tp!=1)p.push_back(tp);
注意是正约数 一开始只局限于质因数中
加的操作 对2^x 可以看作进一位
所以最后的完全平方数是2^2k的形式很好实现
*/
int tp=n;
int sq=sqrt(tp);
vector<int>ans;
while (sq*sq!=tp)
{
int x=lowbit(tp);
tp+=x;
ans.push_back(x);
sq=sqrt(tp);
}
cout<<ans.size()<<endl;
for(auto x:ans)cout<<x<<' ';cout<<endl;
}
K 树形DP
大致思路是先赋一个点,然后向外拓展。问题在于先赋的点值多少。
需要发现点之间权值的关系性质,框一个先赋点值合法范围。
cpp
const int N = 2e5+5,M=1e5;
const double PI=acos(-1);
const long long mod =998244353, inf = 1e18 ,up=1e9;
struct edge{
int u,v,w;
};
vector<pii>g[N];
int wsm[N],dep[N];
/*
大致思路是先赋一个点 然后向外拓展
但是注意判断合法不能只靠一个特例,而是有一个合法区间
wsm是常数部分 偶数深度ans_i=wsm_i-x 奇数深度ans_i=wsm_i+x (根节点深度1)
x是第一个赋值的点 可以随便设为点1 1<=x<=1e9 (右半因为w<=1e9)
限制ans_i合法
1<=wsm_i+x<=1e9 -> 1-wsm_i<=x<=1e9-wsm_i
1<=wsm_i-x<=1e9 -> wsm_i-1e9<=x<=wsm_i-1
又对x有一个限制
*/
void solve(){
int n;cin>>n;
vector<edge>a(n);
int mn=inf;
forr(i,1,n-1){
int u,v,w;cin>>u>>v>>w;
a[i]={u,v,w};
g[u].push_back({v,w});
g[v].push_back({u,w});
mn=min(mn,w);
}
if(n==1){
yes;cout<<1<<endl;
return;
}
/* 一开始直接取权值最小的一个端点直接赋值为1 wa3
sort(a.begin()+1,a.end(),[&](edge x,edge y){
if(x.w==y.w){
return g[x.u].size()+g[x.v].size()<g[y.u].size()+g[y.v].size();
}else return x.w<y.w;
});
if(mn<=1)return no,void();
auto dfs=[&](auto &&dfs,int now,int fa)->bool{
for(auto [x,w]:g[now]){
if(x==fa)continue;
ans[x]=w-ans[now];
if(w[x]<=0)return false;
if(!dfs(dfs,x,now))return false;
}
return true;
};
int rt;
if(g[a[1].u].size()<g[a[1].v].size())rt=a[1].u;
else rt=a[1].v;
ans[rt]=1;
if(dfs(dfs,rt,-1)){
yes;
forr(i,1,n)cout<<ans[i]<<' ';cout<<endl;
}else no;
*/
auto dfs=[&](auto &&dfs,int now,int fa)->void{
dep[now]=dep[fa]+1;
for(auto [x,w]:g[now]){
if(x==fa)continue;
wsm[x]=w-wsm[now];
dfs(dfs,x,now);
}
};
dfs(dfs,1,-1);
int l=1,r=up; //[l,r]是ans_1的范围
forr(i,1,n){
if(dep[i]&1){ // ans_奇数=wsm_i+x
l=max(l,1-wsm[i]);
r=min(r,up-wsm[i]);
}else{
l=max(l,wsm[i]-up);
r=min(r,wsm[i]-1);
}
}
if(l<=r){// 有合法ans_1
yes;
cout<<l<<' ';
forr(i,2,n){
if(dep[i]&1)cout<<wsm[i]+l<<' ';
else cout<<wsm[i]-l<<' ';
}
cout<<endl;
}else no;
}
C 二分答案 贪心 转化问题

一开始想直接找出合并规律,优先合并小的,但是同时考虑数值大小和位置选择太复杂了
中位数只关心相对大小(排名),最后求最大可能值,可以二分答案。数组中大于等于目标答案占多数,最后能合并得到大于目标答案的数,不等关系可以用01表示。
cpp
void solve(){
int n,mx=0;cin>>n;
vector<int>a(n+1);
forr(i,1,n)cin>>a[i],mx=max(mx,a[i]);
/*
二分 枚举最后可能的答案x
贪心 如果最后能化成>=x的数占大部分 则这个答案能化出来
*/
auto check=[&](int x)->bool{
vector<int>st;
forr(i,1,n){ // >=x的数设为1 <x设为0
st.push_back(a[i]>=x);
if(st.size()>=3){
string now="000"; // 模拟 对每个三元组判定中位数结果
int cnt=0;
reforr(j,0,2){
cnt+=st.back();
now[j]=char(st.back()+'0'); // now是后三个的正序
st.pop_back();
}
if(cnt>=1){
/*
cnt=3 111
cnt=2 110 011 101
以上 中位数合并后为1
cnt=1 100 001 010 合并后得0 损失一个1
尽量让0不连续 会让合并后中位数为1 优先合并000
100 可能之后再出现一个0 形成1000 合并成10 不用损失1
001 前面000已经统统合并了 必是00连续 直接合并001 消除这个连续
010 和100同理 后面可能再产生000
*/
// 合并
if(now=="001")st.push_back(0);
else{ // 其他的放回
forr(j,0,2){
st.push_back(now[j]-'0');
}
}
}else st.push_back(0); // cnt=0 000
}
}
int cnt1=0;
for(auto i:st){
cnt1+=i;
}
return cnt1>st.size()-cnt1;
};
int l=1,r=mx,ans;
while (l<=r)
{
int mid=(l+r)>>1;
if(check(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
}
E 前缀和 贪心 查找

总和不变获得和较小的一组,其实就是想让两组的和接近
需要发现插入和抽出对得分的影响:之后的奇偶位置交换
cpp
void solve(){
int n;cin>>n;
vector<int>a(2*n+1),odd(n+1,0),even(n+1,0);
int sm=0;
forr(i,1,2*n){
cin>>a[i];
sm+=a[i];
// int id=(i+1)/2;
int id;
if(i&1)id=(i+1)/2,odd[id]=odd[id-1]+a[i];
else id=i/2,even[id]=even[id-1]+a[i];
}
int dis=odd[n]-even[n],mndis=abs(dis);// 初始 even odd位置的差值 sm=2*ans+dis ans=(sm-dis)/2
set<int>out[2];
forr(i,1,2*n){// 抽出/插入i位置的牌 i+1往后奇偶性反转 (一开始读假题以为是交换两张牌)
/*
dis=pre_odd+suf_odd-pre_even-suf_even
从j(j<i)位置抽出 插入i后一个位置
i、j同奇偶性 1 2 3 4 => 1 2 4 抽出3 => 1 3 2 4 插入1后
ndis=abs(( odd_j + even_i-even_j + odd_n-odd_i )-( even_j + odd_i-odd_j + even_n-even_i ))
=abs(( odd_n-even_n )+ 2*odd_j - 2*even_j - 2*odd_i + 2*even_i)
=abs( dis + 2*( odd_j - even_j ) - 2*( odd_i - even_i ))
=abs((dis - 2*( odd_i - even_i ))- 2*( even_j -odd_j ))
*/
// int id=(i+1)/2;
// int ndis=dis-2*(odd[id]-even[id]);// 插入i后
int ndis=dis-2*(odd[(i+1)/2]-even[i/2]);// 插入i后
auto x=out[i%2].lower_bound(ndis);// 找差值最小
if(x!=out[i%2].end())mndis=min(mndis,abs(ndis-*x));
if(x!=out[i%2].begin())mndis=min(mndis,abs(ndis-*(--x)));// 最小差值也可能出现在前面
// cout<<mndis<<endl;
out[i%2].insert(2*(even[i/2]-odd[(i+1)/2]));
}
cout<<(sm-mndis)/2<<endl;
}