A
思路
题目要构造一个式子:。考虑找一个最大的使得,剩下的部分用补,如果,就把换为继续用补剩下的。判断是否能凑出来。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
ll a,b,n,s;
void solve()
{
sf(a)sf(b)sf(n)sf(s);
ll x=s/n;
ll fir=x*n;
if(x<=a && s-fir<=b)
{
puts("YES");
return;
}
else if(s-fir>b)
{
puts("NO");
return;
}
else if(x>a)
{
if(s-a*n>b)
{
puts("NO");
return;
}
else
{
puts("YES");
return;
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
B
思路
要让字典序最小,就要把小的数字尽量交换到前面。题目给出的是一个序列,无重复元素,因此从小到大枚举数字,能左移就左移,直到前一个数字比当前数字小或者前面的交换机会已经用过。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=104;
int n;
int a[N],ava[N];
void solve()
{
scanf("%d",&n);
rep(i,1,n)
{
scanf("%d",a+i);
}
rep(i,1,n)
ava[i]=1;
int cnt=0;
rep(cur,1,n)
{
rep(i,1,n)
{
if(a[i]!=cur)
continue;
int pos=i;
while(ava[pos-1] && a[pos-1]>a[pos])
{
++cnt;
swap(a[pos-1],a[pos]);
ava[pos-1]=0;
--pos;
}
}
}
rep(i,1,n)
printf("%d ",a[i]);
printf("\n");
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
C
思路
木板总长度不超过,因此可以想象成在木板间插缝。缝的个数最少是,最多是,要让缝平均宽度最小(更好跳),取作为缝的个数,则缝的平均宽度为。因为有可能除不尽有剩余,因此还要计算一下剩余宽度,由该式可知,因此将前个缝的宽度增加,就可以保证最终没有剩余的空间。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1005;
ll n,m,d,sum;
ll c[N],a[N];
int main()
{
sf(n)sf(m)sf(d);
rep(i,1,m)
{
sf(c[i]);
sum+=c[i];
}
ll dis=(n-sum)/(m+1);
ll res=(n-sum)%(m+1);
if((res && dis+1>d-1) || dis>d-1)
{
puts("NO");
return 0;
}
int cur=0;
for(int i=dis;i<n;i+=dis)
{
if(res)
{
++i;
--res;
}
int j;
++cur;
for(j=i+1;j<=i+c[cur];++j)
a[j]=cur;
i=j-1;
}
puts("YES");
rep(i,1,n)
printf("%lld ",a[i]);
puts("");
}
D
思路
贪心,将第一个(非前缀,下同)前面的尽量右移就可以得到最优结果,每个前面所有都可以整体右移一步,那么移动的次数就是前面的个数。每次遇到就计算出当前移动的总次数,直到其大于。那么前面扫过的非前缀的个数就是扫描后前缀增加的个数(因为这些前面的都可以在步内右移,相当于这个冒泡到左边去了)。剩下的步数必定小于当前前面的个数,因此对于剩下的暴力右移即可。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1E6+10;
ll n,k,cnt,sum,zero,one,p;
string s;
void print(string cur)
{
rep(i,1,n)
printf("%c",cur[i]);
puts("");
}
void solve()
{
scanf("%lld%lld",&n,&k);
cin>>s;
s=' '+s;
cnt=sum=zero=one=0;
rep(i,1,n)
{
if(s[i]=='0')
{
++zero;
sum+=one;
if(sum>k)
{
sum-=one;
p=i-1;
--zero;
break;
}
}
else
++one;
}
rep(i,1,zero)
s[i]='0';
rep(i,zero+1,zero+one)
s[i]='1';
k-=sum;
for(int i=min(p,n-1);i>zero;--i)
{
if(k)
{
swap(s[i],s[i+1]);
--k;
}
else
break;
}
print(s);
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
E
思路
先将原数组排序,设为最后一个元素为第个元素时得到的最小代价,通过严谨的数学推理(样例打表)发现,数组递增。第个元素可以作为以第个元素(为结尾的组合中的下一个组合,代价为;此外,第个元素还可以合并到以第个元素为结尾的组合中,代价为。在两者中取最小值即可。
如果暴力枚举,那么复杂度是。但是可以发现每次取的都是的最小值,那么在枚举的之后直接维护一下这个最小值即可,复杂度降为。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
#define pii pair<ll,ll>
typedef long long ll;
using namespace std;
const int N=2E5+10;
ll n,pos,tot;
ll f[N],pre[N],ans[N];
struct Node{
ll val,p;
}a[N];
void dfs(int cur)
{
++tot;
rep(i,pre[cur]+1,cur)
ans[a[i].p]=tot;
if(pre[cur])
dfs(pre[cur]);
}
bool cmpv(const Node &x,const Node &y)
{
return x.val<y.val;
}
int main()
{
sf(n)
rep(i,1,n)
{
sf(a[i].val)
a[i].p=i;
}
sort(a+1,a+n+1,cmpv);
rep(i,4,n)
f[i]=LONG_LONG_MAX;
f[3]=a[3].val-a[1].val;
ll mini=f[3];
rep(i,4,n)
{
if(i-3>=3 && mini>f[i-3]-a[i-2].val)
{
pos=i-3;
mini=f[i-3]-a[i-2].val;
}
if(f[i]>mini+a[i].val)
{
pre[i]=pos;
f[i]=mini+a[i].val;
}
if(f[i]>f[i-1]+a[i].val-a[i-1].val)
{
pre[i]=pre[i-1];
f[i]=f[i-1]+a[i].val-a[i-1].val;
}
}
dfs(n);
printf("%lld %lld\n",f[n],tot);
tot=0;
rep(i,1,n)
printf("%lld ",ans[i]);
}
F
思路
首先找规律。如果两个字符串含有的每个字符的数目不同,那么不管怎么操作都不可能一样。还发现如果其中一个字符串(称为串,另一个称为串)含有两个及以上相同的字母,那么就一定可以把两个相同字母放在一起,然后每次交换串中的两个字母,对应地交换串中已经放在一起的相同的两个字母,这样就保证每次交换之后,串保持不变,串不断交换两个相邻字母最终变成。
由于只要字符串长度超过就一定会出现相同的字母,因此所有长度大于且每个字母出现次数相等的两个字符串一定可以转化成同一个字符串。对于长度不超过的字符串各种暴力都可做。
最后一个阶段除了暴力也有一种复杂度低的算法,考虑将两个字符串都转化为字典序最小的情况,每次只交换相邻的两个字符。记串的转化次数为,串的转化次数为。只要为偶数即可。假设,对于前次操作两者可以同时进行,之后串变为目标串。接下来继续对串操作次,因为每次操作都要对造成影响,为了使串仍保持字典序最小,可以串中的任意选择两个元素进行交换,等下一次交换再把它们换回原先的位置,这样串每进行次交换都可以让串中的这两个元素先错位后复原,字典序仍最小。因此只要多余的操作为偶数即可保证两串都可以转变为目标串。
如何求和呢?他们实际上就是原数组逆序对的个数,利用树状数组分别求解即可,复杂度为。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
#define lb(x) (x)&(-x)
typedef long long ll;
using namespace std;
const int N=2E5+10;
ll n;
ll a[N],b[N],ta[30],tb[30],t[30];
string s;
void add(ll x)
{
while(x<=26)
{
++t[x];
x+=lb(x);
}
}
ll query(ll x)
{
ll ret=0;
while(x)
{
ret+=t[x];
x-=lb(x);
}
return ret;
}
void solve()
{
sf(n)
cin>>s;
int cf=0,ne=0;
rep(i,1,n)
a[i]=b[i]=0;
rep(i,0,26)
ta[i]=tb[i]=t[i]=0;
rep(i,1,n)
{
int cur=s[i-1]-'a'+1;
a[i]=cur;
++ta[cur];
}
cin>>s;
rep(i,1,n)
{
int cur=s[i-1]-'a'+1;
b[i]=cur;
++tb[cur];
}
rep(i,1,26)
{
if(ta[i]!=tb[i])
{
ne=1;
break;
}
}
if(ne)
{
puts("NO");
return;
}
rep(i,1,26)
if(ta[i]>1)
cf=1;
if(cf)
{
puts("YES");
return;
}
ll stepa=0,stepb=0;
rep(i,1,n)
{
ll cur=a[i];
stepa+=query(26)-query(cur-1);
add(cur);
}
rep(i,0,26)
t[i]=0;
rep(i,1,n)
{
ll cur=b[i];
stepb+=query(26)-query(cur-1);
add(cur);
}
if(abs(stepa-stepb)%2)
puts("NO");
else
puts("YES");
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}