后天就是 I C P C ICPC ICPC杭州站了,今天把之前做的 d i v 3 div3 div3题补一下,打完这场杭州站这赛季除了 E C F i n a l EC\,\,Final ECFinal就结束了,以后应该要多打 c f cf cf比赛练习保持手感,争取下赛季冲一下金牌。
感觉这个 d i v 3 div3 div3的难度还不错,正常状态应该能做到差一题 A K AK AK,思维含量还没有太高,适合我这种fw选手。
题面
题意:一个空的国际象棋棋盘,给你车的初始位置,问你挪动一步这个车可以到达哪些位置。
S o l u t i o n : Solution: Solution:按行按列输出即可,注意初始的点不要输出。
#include
using namespace std;
typedef long long ll;
const int N = 1e5+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t;
char chr,ch;
void solve(){
chr=getchar();
while(chr<'a'||chr>'z')chr=getchar();
ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
n=ch-'0';
for(int i=1;i<=8;i++){
if(i==n)continue;
printf("%c%d\n",chr,i);
}
for(int i=1;i<=8;i++){
if('a'+i-1==chr)continue;
printf("%c%d\n",'a'+i-1,n);
}
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:一个奇怪的键盘,按小写 b b b是删掉目前已经打的最后一个小写字母,按大写 B B B是删掉目前已经打的最后一个大写字母(如果没有则不删,打 b / B b/B b/B不会打出来字符只会删掉字符),给出键盘按下的顺序,求最后打出的内容。
S o l u t i o n Solution Solution:个人做法是从头开始记录目前最后一个小写、大写字母的位置,然后遇到 b / B b/B b/B就删除那个位置的字符,并更新目前最后一个小写、大写字母的位置,写的比较麻烦。赛后看题解发现倒序维护 b / B b/B b/B的数量,遇到大小写直接删除是最快的最方便的。
#include
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t,len,l[N],L[N],p,P,lst[N],LST[N];
char s[N];
void solve(){
scanf("%s",s);
len=strlen(s);
p=P=0;
lst[0]=LST[0]=-1;
for(int i=0;i<len;i++){
if(s[i]>='A'&&s[i]<='Z'){
if(s[i]=='B'){
if(P){
s[L[P]]=0;
P--;
}
s[i]=0;
}
else L[++P]=i;
}
else{
if(s[i]=='b'){
if(p){
s[l[p]]=0;
p--;
}
s[i]=0;
}
else l[++p]=i;
}
}
for(int i=0;i<len;i++){
if(s[i]!=0)putchar(s[i]);
}
puts("");
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:对于一个字符串可以进行的操作是删掉相邻两个不相同的字符,问最后最少剩下几个字符。
S o l u t i o n Solution Solution:之前做过类似结论的题,对于出现最多的字符的出现次数 t t t,如果 t ≤ ⌊ l e n 2 ⌋ t\le\lfloor\frac{len}{2}\rfloor t≤⌊2len⌋,则不会剩下字符,否则剩下字符数为 t − ( t − ⌊ l e n 2 ⌋ ) t-(t-\lfloor\frac{len}{2}\rfloor) t−(t−⌊2len⌋)。
#include
using namespace std;
typedef long long ll;
const int N = 2e5+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t;
int cnt[30];
char s[N];
void solve(){
read(n);
for(int i=0;i<=26;i++)cnt[i]=0;
for(int i=1;i<=n;i++){
s[i]=getchar();
while(s[i]<'a'||s[i]>'z')s[i]=getchar();
cnt[s[i]-'a']++;
}
int m=0;
for(int i=0;i<=26;i++){
if(cnt[i]>m)m=cnt[i];
}
if(m<=n-m){
if(n&1)puts("1");
else puts("0");
}
else printf("%d\n",m-n+m);
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:一个坐标轴上有 n n n条线段,从原点 0 0 0开始移动 n n n次,每次移动到第 i i i条线段上,这些移动的最大距离 ≤ k \le k ≤k,求 k k k的最小值。
S o l u t i o n Solution Solution:二分 k k k,判断 k k k是否可行我们通过一个贪心的思想来模拟。
首先对于当前位置 n o w now now,判断其在目标线段的左侧还是右侧,如果在左侧则要向左移动,在右侧则要向右移动,这两种情况是等价的我们以向右移动为例子。
如果向右移动到目标线段的左侧,发现还可以继续移动,这时我们就维护一个 l , r l,r l,r,代表目前可以向左、右走的多余距离。例如上述情况我们的 l = 0 , r = l=0,r= l=0,r=剩余的移动距离。
每当我们移动距离 k k k发现仍然到不了目标线段时,此时就要用到这个多余距离来判断能否通过上一步剩余的距离来使我们移动到目标线段。
对于当前位置正好在目标线段的情况,向左向右的多余距离分别为该位置到左右端点的距离。
#include
using namespace std;
typedef long long ll;
const int N = 2e5+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t,l[N],r[N];
bool check(int x){
int now=0,suml=0,sumr=0,movl=0,movr=0;
for(int i=1;i<=n;i++){
int cnt=0;
if(now<l[i]){
cnt=l[i]-now;
movr=min(r[i]-l[i],x-cnt);
if(movr<0){
if(sumr+movr<0)return false;
}
sumr+=movr,sumr=min(sumr,r[i]-l[i]),suml=0;
now=l[i];
}
else if(now>r[i]){
cnt=now-r[i];
movl=min(r[i]-l[i],x-cnt);
if(movl<0){
if(suml+movl<0)return false;
}
suml+=movl,suml=min(suml,r[i]-l[i]),sumr=0;
now=r[i];
}
else{
suml=min(x+suml,now-l[i]),sumr=min(x+sumr,r[i]-now);
}
}
return true;
}
void solve(){
read(n);
for(int i=1;i<=n;i++)read(l[i]),read(r[i]);
int le=0,ri=1e9,mid=0;
while(le<ri){
int mid=le+ri>>1;
if(check(mid))ri=mid;
else le=mid+1;
}
printf("%d\n",ri);
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:给你 n n n 问你有多少组 ( a , b , c ) (a,b,c) (a,b,c)使得 a + b + c = n a+b+c=n a+b+c=n 并且 a , b , c a,b,c a,b,c的每个位数的和等于 n n n的位数的和。
S o l u t i o n : Solution: Solution:很显然每一位不能有进位,以为数字和相等,所以对每一位都是独立考虑,各个位之间的方案数乘起来即是答案。
对于每一位的答案,赛时是通过观察样例前几个得到的答案。其实推也很好推,不想推一个一个写也写出来了,对于这一位为 i i i,答案就是 0 + 1 + ⋯ + i 0+1+\dots+i 0+1+⋯+i。
#include
using namespace std;
typedef long long ll;
const int N = 2e5+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t,a[]={1,3,6,10,15,21,28,36,45,55};
void solve(){
read(n);
ll s=1;
while(n){
s*=1ll*a[n%10];
n/=10;
}
printf("%lld\n",s);
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:对于一个序列,你可以做两种操作,第一种是把最后一个放到最前面,第二种是把整个序列倒序,问最少用几次操作可以把数列变为从小到大的有序序列。不能变则输出 − 1 -1 −1。
S o l u t i o n Solution Solution:如果把这个序列当成循环序列,不难发现第一种操作没有改变序列,第二种操作只是改变了顺序和倒序,因此如果序列最开始不是顺序或逆序,则不能变为有序数列。
对于可以改变的序列,我们要找到序列的起始点,再分别判断先改变位置再倒序和先倒序再改变位置哪个更优,输出操作次数最少的那个即可。
赛时有点困想出来的时候还有 5 5 5分钟,再加上想出来的方法比较麻烦所以就没做这个题,实际上直接倍长数组就比较方便。
#include
using namespace std;
typedef long long ll;
const int N = 2e5+10;
const ll mod = 998244353;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,t,a[N<<1];
void solve(){
read(n);
for(int i=1;i<=n;i++)read(a[i]),a[i+n]=a[i];
int b=1,s=1,now=0,ans=n<<1;
for(int i=2;i<=n*2;i++){
if(a[i]>a[i-1])b++,s=1;
else if(a[i]<a[i-1])s++,b=1;
else s++,b++;
if(s>=n||b>=n){
now=i-n+1;
if(s>=n)ans=min(ans,min(1+n-now+1,now));
if(b>=n)ans=min(ans,min(n-now+1,2+now-1));
}
}
if(ans!=n*2)printf("%d\n",ans);
else puts("-1");
}
int main(){
read(t);
while(t--)solve();
return 0;
}
题面
题意:你有 n n n个灯,有开有闭,想把这些灯全关了,但是灯有个性质是每按一次灯 i i i开关,灯 a i a_i ai的开关也会被按一次,问你最少多少次能把这些灯全关了,不能全关输出 − 1 -1 −1。
S o l u t i o n Solution Solution:把 i i i和 a i a_i ai连出一条边,就会获得一个 n n n个点 n n n条边的一张图,这个图会由若干棵基环树组成,对于每棵树我们先处理他的叶子节点,随后删掉叶子节点,最后剩下一个环,在这个环上我们每次操作都是动偶数个灯,因此如果环上有奇数个灯开着那么就无法全部关闭,输出 − 1 -1 −1。对于环上有两种情况,一种是从起点开始关,一种是从起点的下一个点开始关,这个起点可以随意设置,因为环上是等价的,选出这两种情况关闭数量最少的一个进行关灯即可。
#include
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
const int N = 2e5 + 100;
int T,n,cd[N],e[N],a[N],cc[N];
void solve(){
read(n);
for(int i=1;i<=n;i++){
char chr=getchar();
while(chr!='0'&&chr!='1')chr=getchar();
cc[i]=cd[i]=chr&15;
}
vector<set<int> > s(n+1);
queue<int> p;
int ans=0;
for(int i=1,x;i<=n;i++){
read(x);e[i]=x;
s[x].insert(i);
}
for(int i=1;i<=n;i++)if(s[i].size()==0)p.push(i);
while(!p.empty()){
int i=p.front();p.pop();
if(cd[i]==1){
s[e[i]].erase(i);
cd[i]^=1,cd[e[i]]^=1;
a[++ans]=i;
if(s[e[i]].size()==0)p.push(e[i]);
}
else{
s[e[i]].erase(i);
if(s[e[i]].size()==0)p.push(e[i]);
}
}
for(int now=1;now<=n;now++){
if(cd[now]){
vector<int> c;
int sum=1;
if(cd[now])c.push_back(1);
for(int i=e[now];i!=now;i=e[i]){
sum++;
if(cd[i])c.push_back(sum);
}
if(c.size()&1){
puts("-1");
return ;
}
int s1=0,s2=c[0]+sum-c[c.size()-1];
for(int i=1;i<c.size();i++){
if(i&1)s1+=c[i]-c[i-1];
else s2+=c[i]-c[i-1];
}
int st=now;
if(s2<=s1)st=e[now];
for(int i=st;;i=e[i]){
if(cd[i]){
a[++ans]=i;
cd[i]^=1,cd[e[i]]^=1;
}
if(e[i]==st)break;
}
}
}
printf("%d\n",ans);
for(int i=1;i<=ans;i++)printf("%d ",a[i]);
puts("");
return ;
}
int main(){
read(T);
while(T--)solve();
return 0;
}