C
思维
每个人都给出除了自己以外,人数最多的颜色有多少人。问能否判断有人说谎
如果只有一组人数最多的颜色,有人,没人撒谎的情况肯定是,这组内
人报告
,这组外其他人报告
如果有多组人数最多的颜色,都有人,那么所有人都报告
。因为至少有两组,所以总人数
不少于
。
还一种可能是个人颜色都一样,所有人都报告
,也是可以的
其它都不合法
void solve(){
int n;
cin>>n;
vi a(n+1);
rep(i,1,n)cin>>a[i];
set<int>s;
map<int,int>mp;
rep(i,1,n){
s.insert(a[i]);
mp[a[i]]++;
}
if(s.size()==1){
if(a[1]==n-1){
cout<<"Other\n";
}
else if(2*a[1]>n){
cout<<"Lie\n";
}
else{
cout<<"Other\n";
}
}
else if(s.size()==2){
int x=*s.begin();
int y=*s.rbegin();
if(y==x+1&&y==mp[x]){
cout<<"Other\n";
}
else{
cout<<"Lie\n";
}
}
else{
cout<<"Lie\n";
}
}
D
多源BFS
找到所有到叶子的最短距离最大的非叶点
从所有叶子开始多源BFS,记录每个非叶点到最近的叶子1的距离
void solve(){
int n;
cin>>n;
vvi g(n+1);
vi in(n+1);
rep(i,1,n-1){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
in[u]++;
in[v]++;
}
queue<int>q;
vi d(n+1,1e9);
rep(i,1,n){
if(in[i]==1){
q.push(i);
d[i]=0;
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int v:g[u]){
if(d[v]>d[u]+1){
d[v]=d[u]+1;
q.push(v);
}
}
}
int mx=0;
vi ans;
rep(i,1,n){
if(d[i]>mx){
mx=d[i];
ans.clear();
ans.push_back(i);
}
else if(d[i]==mx){
ans.push_back(i);
}
}
cout<<ans.size()<<'\n';
for(int x:ans){
cout<<x<<' ';
}
cout<<'\n';
}
E
数论
找一个子序列,满足相邻元素要么要么
,问最长序列
显然子序列,转移时
可以从所有
的因子,或者
的倍数转移过来,因子可以直接枚举,倍数,由于不保证元素不重,枚举复杂度可能炸了。
但是每个往前且是倍数的转移,实际上从转移前驱来看,等价于往后的因子转移,所以可以记录每个因子的前驱倍数的最优解。每个
的
结束后,分解
,更新所有因子的
void solve(){
int n;
cin>>n;
vi a(n+1);
rep(i,1,n){
cin>>a[i];
}
vi dp(n+1),mx(n+1);
int ans=0;
rep(i,1,n){
int res=dp[a[i]];
rep(j,1,sqrt(a[i])){
if(a[i]%j==0){
res=max(res,dp[j]+1);
if(a[i]/j!=j){
res=max(res,dp[a[i]/j]+1);
}
}
}
res=max(res,mx[a[i]]+1);
dp[a[i]]=res;
rep(j,1,sqrt(a[i])){
if(a[i]%j==0){
mx[j]=max(mx[j],dp[a[i]]);
if(a[i]/j!=j){
mx[a[i]/j]=max(mx[a[i]/j],dp[a[i]]);
}
}
}
ans=max(ans,res);
}
cout<<ans<<'\n';
}
F
贪心 增量 堆
每个长度为的区间贡献是
,可以把一些点的颜色反转,让区间断开。问最多操作
次后的最小答案?
一个错误的贪心是,每次取出最长的区间,从中间断开。这虽然在只有一次操作的情况下是最优的,但不是全局最优的。考虑一个长度8的,按我们这个策略操作两次后是,但实际最优的是
,也就是尽可能均匀的切分成三段
所以,我们实际上是要对于每个区间,决定操作几次,如果决定操作一个区间次,就要尽可能均分地把他分成
份
这里有一个重要性质,对于一个区间,我们给他增加操作次数,边际效益是递减的,也就是增加一次操作,这个区间贡献地减小量,是递减的
那么问题也就转化成,有个区间的话,我们有
个序列
,其中
表示,第
个区间操作次数从
增加到
次,这个区间的贡献的减小量,每个
都是递减的,每一个我们都要取一个前缀,总的选取的前缀长度加起来为
,使得选中的元素和最大(我们这里是减小量最大,也就使得原问题中的剩余贡献最小)。
这是个经典问题,每个序列都是单调的,那么我们一个堆维护所有序列的开头的最值,那么堆顶就是个序列里所有剩余元素的最大值,这样每次取出的都是全局最大值,取
次取走了最大的
个。这贪心是对的
注意这个策略的正确性依赖于每个序列都是单调的,如果不单调就不对了
然后一个子问题是,长度
的区间,操作
次的最小贡献,如何计算。
显然均分的话,较小段长度为,然后有
段长度为
,剩余
段长度为
,这是经典贪心不谈
int s(int x){
return x*(x+1)/2;
}
int f(int x,int y){
int tot=x-y;
if(tot<0)return 0;
int mn=tot/(y+1);
int mx=mn+1;
int cntmx=tot%(y+1);
int cntmn=y+1-cntmx;
return cntmx*s(mx)+cntmn*s(mn);
}
void solve(){
int n,k;
cin>>n>>k;
string s;
cin>>s;
s=' '+s;
s+='x';
int len=0;
multiset<array<int,3>>q;
int ans=0;
rep(i,1,n+1){
if(s[i]=='o'){
len++;
}
else{
q.insert({f(len,0)-f(len,1),len,0});
ans+=len*(len+1)/2;
len=0;
}
}
while(k--&&q.size()){
auto u=*q.rbegin();
q.erase(--q.end());
int delta=u[0],len=u[1],del=u[2];
del++;
ans-=delta;
if(del<=len){
q.insert({f(len,del)-f(len,del+1),len,del});
}
}
cout<<ans<<'\n';
}