A. Recent Actions
注意到只有编号大于n的博客会被更新,所以每当有一个之前没被更新的过的博客被更新时,当前列表中最下面的就会被挤掉。如果这次更新的博客之前已经被更新过,那么此时仍在列表中的编号为1-n的博客的顺序不会被改变。所以直接模拟即可。
时间复杂度\(O(n+m)\)。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
int t,n,m,p[50010],in[100010],ans[50010];
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>n>>m;
repn(i,m) scanf("%d",&p[i]);
rep(i,n+3) ans[i]=-1;
rep(i,n+m+3) in[i]=0;
LL cc=0;
repn(i,m)
{
if(in[p[i]]) continue;
in[p[i]]=1;
++cc;
if(n-cc+1>=0) ans[n-cc+1]=i;
}
repn(i,n) printf("%d ",ans[i]);
puts("");
}
termin();
}
B. Equalize by Divide
首先特判所有元素已经相等的情况。其余情况,如果初始最小的数是1,那就是无解,因为你考虑最后一步操作需要把一个不是1的数变成1,但是除以1只能得到本身,是不能变成1的。其余情况,发现只要在数组中还有不相同的元素时任意拿出两个不相同的,并把大的除以小的(保证不会得到1),不断进行下去,总能使得所有元素相等。所以还是直接模拟。
时间复杂度\(O(30n)\)。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
int t,n,a[110];
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>n;
rep(i,n) cin>>a[i];
if(n==1)
{
cout<<0<<endl;
continue;
}
int mx=-2e9,mn=2e9;
rep(i,n) mx=max(mx,a[i]),mn=min(mn,a[i]);
if(mn==1&&mx>1)
{
puts("-1");
continue;
}
vector <pii> ans;
while(true)
{
pii mnv=mpr(2e9,2e9),mxv=mpr(-2e9,-2e9);
rep(i,n) mnv=min(mnv,mpr(a[i],i)),mxv=max(mxv,mpr(a[i],i));
if(mxv.fi==mnv.fi) break;
ans.pb(mpr(mxv.se,mnv.se));
a[mxv.se]=(a[mxv.se]+a[mnv.se]-1)/a[mnv.se];
}
cout<<ans.size()<<endl;
rep(i,ans.size()) cout<<ans[i].fi+1<<' '<<ans[i].se+1<<endl;
}
termin();
}
C. Double Lexicographically Minimum
现在的cf,真的是,很多东西只能靠猜。
我们发现构造字符串的策略肯定是,从两边贪心地往中间填,每次尽量填最小的字母。如果某一次填的两个字母不同,就把大的放左边、小的放右边,然后把剩下所有没填的字母从小到大排序放中间,这时就已经得到了答案,可以结束贪心的过程了。
然后来细说怎么贪心。如果当前剩余的最小的字母还剩不止1个,那就前后各填一个这种字母;否则看第二小的字母,如果它也只剩一个,那就填上这两个字母,然后按照上面的方式结束贪心。剩下一种情况,最小的字母只有1个,但第二小的不止一个,这种我们可以观察一下猜个结论:如果剩下的字母除了这两种没有别的,那就前后各填一个第二小的字母然后继续贪心;否则就就填上这两种字母然后按照上面的方式结束贪心。我也不知道这个怎么证明,反正手画了几组没有问题,交上去也是对的。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
int t,cnt[30];
string s,ff,bb;
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>s;
rep(i,28) cnt[i]=0;
rep(i,s.size()) ++cnt[s[i]-'a'];
ff=bb="";
while(true)
{
int c1=-1,c2=-1;
rep(i,26) if(cnt[i])
{
if(c1>-1){c2=i;break;}
if(cnt[i]>1){c1=c2=i;break;}
c1=i;
}
if(c1==-1) break;
if(c2==-1)
{
ff.pb(c1+'a');
break;
}
if(c1==c2){ff.pb(c1+'a');bb.pb(c1+'a');cnt[c1]-=2;}
else
{
bool ok=(cnt[c2]>1);
for(int jj=c2+1;jj<26;++jj) if(cnt[jj]) ok=false;
if(ok){ff.pb(c2+'a');bb.pb(c2+'a');cnt[c2]-=2;}
else
{
ff.pb(c2+'a');bb.pb(c1+'a');
--cnt[c1];--cnt[c2];
rep(i,26) rep(j,cnt[i]) ff.pb(i+'a');
break;
}
}
}
for(int i=(int)bb.size()-1;i>=0;--i) ff.pb(bb[i]);
cout<<ff<<endl;
}
termin();
}
D2. Hot Start Up (hard version)
如果对于某两个(i,j),\(a_i=a_j\)且我们想要在某个CPU上连续运行i和j,那就说明\(i+1\cdots j-1\)都不能用这个CPU了。对于任意(i,j)满足\(a_i=a_j\),我们把"i与j连续运行"这件事看成一条线段,覆盖区间\([i+1,j-1]\),它的权值为\(cold_{a_i}-hot_{a_i}\),代表能省下这么多时间。我们需要选择一些线段,使得没有任意一个位置被一条以上的线段覆盖,同时最大化所有选出线段的权值和。还有一个事情就是如果\(a_i=a_j\),且存在k使得\(i<k<j,a_i=a_k\),那选择\([i+1,j-1]\)这条线段一定不好,所以不需要考虑这种情况。因此,我们把所有\(O(n)\)条线段处理出来,然后做一个简答的dp就行了。
时间复杂度\(O(n)\)。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
LL t,n,k,a[300010],c[300010],h[300010],dp[300010],add[300010];
vector <LL> v[300010];
vector <pii> ques[300010];
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>n>>k;
rep(i,k+3) v[i].clear();
rep(i,n+3) ques[i].clear();
rep(i,n)
{
scanf("%lld",&a[i]);--a[i];
v[a[i]].pb(i);
}
rep(i,k) scanf("%lld",&c[i]);rep(i,k) scanf("%lld",&h[i]);
LL radd=0;
rep(i,k) if(v[i].size()>1) rep(j,v[i].size()-1)
{
if(v[i][j]+1==v[i][j+1]) radd+=c[i]-h[i];
else ques[v[i][j]+1].pb(mpr(v[i][j+1]-1,c[i]-h[i]));
}
rep(i,n+3) dp[i]=-1e18;
rep(i,n+3) add[i]=0;
LL cur=0;
rep(i,n+1)
{
cur=max(cur,add[i]);
if(i==n) break;
rep(j,ques[i].size()) add[ques[i][j].fi+1]=max(add[ques[i][j].fi+1],cur+ques[i][j].se);
}
LL ans=0;rep(i,n) ans+=c[a[i]];
ans-=cur+radd;
cout<<ans<<endl;
}
termin();
}
E. City Union
乍一看好像是个比较弱智的题,其实需要一些思考,程序也挺难写的。
"任意两个黑方块之间的最短距离等于它们的曼哈顿距离"其实等价于"不存在一个白方块,使得它的上下两侧都有黑方块,或者它的左右两侧都有黑方块"。这也是很好理解的。所以我们不断地检查有没有这样的白方块,如果有的话就涂黑,知道找不出这样的白方块位置。这一步怎么实现都行,反正复杂度应该不会高于\(O(n^4)\)。这一步做完后先进行一个bfs,如果整张图已经连通,直接输出就行了。否则,此时的图一定是两坨黑色的方块,一个在左上一个在右下,或者一个在右上一个在左下。比如一个左上一个右下的情况是这样的:
就以这种情况举例。接下来我们要涂黑最少的方块使得两坨东西连通,且不存在上面说的那种白方块。首先涂黑下图中的红色区域,它们是必须被涂黑的。也就是左上一坨的右下角,和右下一坨的左上角:
然后取出左上一坨当前最靠右下的那个位置,以及右下一坨当前最靠左上的那个位置,把它们用任意路径连起来即可。这一步涂黑的方块个数是它们之间的曼哈顿距离-1。可以发现这样构造一定是最优的。
实现就见仁见智了,可能会有点麻烦(听说有人写了9k的,哈人)。我写了3k多点,还算比较简洁。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
void chmax(int &x,int y){if(x<y) x=y;}
void chmin(int &x,int y){if(x>y) x=y;}
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int t,n,m,rMost[60],dMost[60];
string s[60];
char c[60];
vector <vector <pii> > v;
vector <pii> tmp;
bool vis[60][60];
void print()
{
rep(i,n)
printf("%s\n",s[i].c_str());
}
bool out(int x,int y){return x<0||x>=n||y<0||y>=m;}
void dfs(int x,int y)
{
vis[x][y]=true;tmp.pb(mpr(x,y));
rep(i,4)
{
int xx=x+dx[i],yy=y+dy[i];
if(out(xx,yy)||vis[xx][yy]||s[xx][yy]=='.') continue;
dfs(xx,yy);
}
}
pii complete(vector <pii> a,pii to)
{
map <int,int> rmn,rmx;
int mnr=2e9,mxr=-2e9;
rep(i,a.size())
{
if(rmn.find(a[i].fi)==rmn.end()) rmn[a[i].fi]=rmx[a[i].fi]=a[i].se;
else chmin(rmn[a[i].fi],a[i].se),chmax(rmx[a[i].fi],a[i].se);
chmin(mnr,a[i].fi);chmax(mxr,a[i].fi);
}
int lr=(to.se<a[0].se ? 0:1),ud=(to.fi<a[0].fi ? 0:1);
pii ret=mpr(0,0);
if(lr==0)
{
pii lmost=mpr(2e9,2e9);
for(auto it:rmn) lmost=min(lmost,mpr(it.se,it.fi));
ret=mpr((ud==0 ? mnr:mxr),lmost.fi);
for(auto it:rmn) if((ud==0&&it.fi<lmost.se)||(ud==1&&it.fi>lmost.se))
{
for(int i=it.se-1;i>=lmost.fi;--i)
s[it.fi][i]='#';
}
}
else
{
pii rmost=mpr(-2e9,-2e9);
for(auto it:rmx) rmost=max(rmost,mpr(it.se,it.fi));
ret=mpr((ud==0 ? mnr:mxr),rmost.fi);
for(auto it:rmx) if((ud==0&&it.fi<rmost.se)||(ud==1&&it.fi>rmost.se))
{
for(int i=it.se+1;i<=rmost.fi;++i)
s[it.fi][i]='#';
}
}
return ret;
}
void walk(pii x,pii y)
{
s[x.fi][x.se]='#';
if(abs(x.fi-y.fi)+abs(x.se-y.se)==1) return;
if(y.fi<x.fi) walk(mpr(x.fi-1,x.se),y);
else if(y.fi>x.fi) walk(mpr(x.fi+1,x.se),y);
else if(y.se<x.se) walk(mpr(x.fi,x.se-1),y);
else walk(mpr(x.fi,x.se+1),y);
}
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>n>>m;
rep(i,n)
{
scanf("%s",c);
s[i]=c;
}
while(true)
{
rep(i,n) rMost[i]=-1;rep(i,m) dMost[i]=-1;
rep(i,n) rep(j,m) if(s[i][j]=='#') chmax(rMost[i],j),chmax(dMost[j],i);
int ok=0;
rep(i,n) rep(j,m) if(s[i][j]=='#')
{
if(j+1<m&&rMost[i]>j&&s[i][j+1]=='.') s[i][j+1]='#',ok=1;
if(i+1<n&&dMost[j]>i&&s[i+1][j]=='.') s[i+1][j]='#',ok=1;
}
if(ok==0) break;
}
v.clear();
rep(i,n+3) rep(j,m+3) vis[i][j]=false;
rep(i,n) rep(j,m) if(s[i][j]=='#'&& !vis[i][j])
{
tmp.clear();
dfs(i,j);
v.pb(tmp);
}
if(v.size()==1)
{
print();
continue;
}
pii p1=complete(v[0],v[1][0]),p2=complete(v[1],v[0][0]);
walk(p1,p2);
print();
}
termin();
}
F. Halve or Subtract
先把原序列中的所有数分成三类:\(\ge 2b\)的为A类,\(<2b,\ge b\)的为B类,\(<b\)的为C类。一个数如果我们要给它做两次操作,那肯定是先1操作再2操作比较划算。我们希望合理地分配这k1+k2次操作,使得它们把原序列减掉的数值尽可能大。
首先对于A类数,把操作施加给它们是很划算的。A类中的每个数不管施加几次操作,其中的2类操作一定能够吃满,也就是产生b的贡献;1类操作也能产生尽量大的贡献。所以我们把A类数从大到小排序,然后给每个数都尽量地施加两种操作,直到k1或k2不够了为止。
令k1和k2分别为处理完A类后剩余的两类操作数。现在就只剩B类和C类的数需要处理了。由于\(n\le5000\),我们可以枚举给B类施加的1操作次数B1和给B类施加的2操作次数B2,剩下的操作全给C类。
先观察B类。发现被施加1操作的一定是B类中最大的B1个数,因为B类中不被施加1操作的数最多只能产生b的贡献(通过施加一次2操作),而B类中所有数都\(\ge b\),所以对于单独的一次2操作来说所有B类数都是一样的。被施加2类操作的数则是所有没被施加1操作的数以及被施加过1操作后剩余的数中最大的B2个。
然后是C类。C类中的数只要被施加了2操作就会变成0,所以对C类中的数施加两种操作完全是浪费。依此可以得出,2操作应施加给C类中最大的C2个数,1操作则施加给剩下的数中最大的C1个(如果C1+C2>|C|也没事,浪费几个1操作好了)。
根据上面的策略\(O(n^2)\)模拟就行了。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
LL t,n,b,k1,k2,bpresum[5010],cbig[5010],csmall[5010];
vector <LL> A,B,C;
int main()
{
fileio();
cin>>t;
rep(tn,t)
{
cin>>n>>b>>k1>>k2;
LL tot=0,x;
A.clear();B.clear();C.clear();
rep(i,n)
{
scanf("%lld",&x);
tot+=x;
if(x>=b+b) A.pb(x);
else if(x>=b) B.pb(x);
else C.pb(x);
}
sort(A.begin(),A.end());reverse(A.begin(),A.end());
LL ans=0;
rep(i,A.size())
{
if(k1)
{
--k1;
LL nn=(A[i]+1)/2;
ans+=A[i]-nn;
A[i]=nn;
}
if(k2)
{
--k2;
ans+=b;
}
}
sort(B.begin(),B.end());reverse(B.begin(),B.end());
sort(C.begin(),C.end());
rep(i,C.size()) csmall[i+1]=csmall[i]+C[i]/2;
reverse(C.begin(),C.end());
rep(i,C.size()) cbig[i+1]=cbig[i]+C[i];
LL opt=0,addi=0;
rep(B1,min(k1+1,(LL)B.size()+1))
{
rep(B2,min(k2+1,(LL)B.size()+1))
{
LL add=addi+b*min((LL)B2,(LL)B.size()-B1);
if(B1+B2>B.size()) add+=bpresum[B1+B2-B.size()];
LL C1=min((LL)C.size(),k1-B1),C2=min((LL)C.size(),k2-B2);
add+=cbig[C2];
if(C1+C2>=C.size()) add+=csmall[C.size()-C2];
else add+=csmall[C.size()-C2]-csmall[C.size()-C2-C1];
opt=max(opt,add);
}
if(B1==B.size()) break;
bpresum[B1+1]=bpresum[B1]+(B[B1]+1)/2;addi+=B[B1]/2;
}
ans+=opt;
cout<<tot-ans<<endl;
}
termin();
}
G. Count Voting
小清新数数题。前面的题搞那么毒瘤,然后G放个水题是我不能李姐的。
先来改编一下题意,有n个人,第i个人有一个写了数字i的球,还有\(c_i\)个盒子,编号为\(1\cdots c_i\)。每个人需要把球放到一个不同组的盒子里,使得每个盒子里都有1个球,求方案数。把这个问题的答案乘上\(\frac1{\prod c_i!}\)就是原问题的答案。接下来我们来求这个问题的答案。
先把所有的人按照组别分类,令一共有m个非空组,第i个组中有\(a_i\)个人和\(b_i\)个盒子。令\(dp_{i,j}\)表示处理了前i个组,前i个组中有j个球是往后m-i个组放的,此时的方案数。由i和j可以得到,前i个组中还有\((\sum_{k=1}^i b_i) -(\sum_{k=1}^i a_i) +j\)个盒子没被填满。转移的时候枚举当前组中有几个盒子是被前i组往后放的球填满的,以及当前组中有几个球是要用来填前i组中的空盒子的。两次枚举的方案数都是两个组合数相乘,再乘上盒子与球配对的一个阶乘。这样其实就做完了,看似四重循环,其实发现时间复杂度是\(O(n^3)\)的。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
const LL MOD=998244353;
LL qpow(LL x,LL a)
{
LL res=x,ret=1;
while(a>0)
{
if(a&1) (ret*=res)%=MOD;
a>>=1;
(res*=res)%=MOD;
}
return ret;
}
LL n,c[210],t[210],cnt[210],valtot[210],fac[210],inv[210],dp[210][210];//dp[位置][向后投票数]
LL cc[210][210];
vector <pii> v;
int main()
{
fileio();
fac[0]=1;repn(i,205) fac[i]=fac[i-1]*i%MOD;
rep(i,203) inv[i]=qpow(fac[i],MOD-2);
rep(i,202) rep(j,i+1) cc[i][j]=fac[i]*inv[j]%MOD*inv[i-j]%MOD;
cin>>n;
rep(i,n) cin>>c[i];
rep(i,n) cin>>t[i],--t[i];
LL mull=1;
rep(i,n) if(c[i]) (mull*=inv[c[i]])%=MOD;
rep(i,n) ++cnt[t[i]],valtot[t[i]]+=c[i];
rep(i,n) if(cnt[i]) v.pb(mpr(cnt[i],valtot[i]));
LL curcnt=0,curtot=0;
dp[0][0]=1;
rep(i,v.size())
{
rep(j,curcnt+1) if(dp[i][j])
{
LL need=curtot-(curcnt-j);//前面的缺口数
rep(k,min(need+1,v[i].fi+1))//填补缺口数
{
LL mul=cc[v[i].fi][k]*cc[need][k]%MOD*fac[k]%MOD;
rep(p,min((LL)j+1,v[i].se+1))//被填补的缺口数
{
LL mul2=cc[j][p]*cc[v[i].se][p]%MOD*fac[p]%MOD;
(dp[i+1][j+v[i].fi-k-p]+=dp[i][j]*mul%MOD*mul2)%=MOD;
}
}
}
curcnt+=v[i].fi;curtot+=v[i].se;
}
cout<<dp[v.size()][0]*mull%MOD<<endl;
termin();
}
最近vp了几场上古的cf,对比一下最近的,有一种世风日下的感觉。实在不理解,这些出题人和验题人真觉得往正式比赛里出各种结论题、猜谜题、模拟题、傻逼构造题是一件很好玩的事情么
标签:854,1799,int,题解,LL,fi,rep,se,define From: https://www.cnblogs.com/legendstane/p/codeforces-round-854-cybercats-cf-1799-a-b-c-d1-d2-e