Codeforces Round #662 (Div. 2)A-D题解
//写于大号rating值2184/2184,小号rating值1881/1881
//本场拿小号打的,名次在div2选手排78,rating值+219
//A白交了一发假结论,D写错两个变量下标白给两发…
//人永远也不能做到不犯低级错误,只能尽可能避免…
比赛链接:https://codeforces.com/contest/1393
A题
过题用时:5min(罚时1发)
简单结论
具体就不解释了,我的思考方式是从n为奇数开始考虑。
容易得到,当n为奇数的时候,n+2的情况比n的情况多需要1次操作。
而n+1的情况构造次数不可能超过n+2的情况。
由此得到n=1对应答案1,n=2,3对应答案2,n=4,5对应答案3…
#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int32_t main()
{
IOS;
int t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
cout<<(n+2)/2<<endl;
}
}
B题
过题用时:13min
施行,模型转化
首先题意不要理解错(比如我一开始就读错了)
题意为是我们一开始用n根各种长度的木棒,经过q次增加一根或减少某根长度的木棒,问在每次增减操作后能否用这些木棒构造出一个长方形和正方形,构造的时候只需要用这些木棒构成长方形和正方形的周长部分即可,并且每条边只能放一根木棒
那么先从正方形和长方形的特点总结一下:
正方形需要4根长度相等的木棒
长方形需要2对分别长度相等的木棒
正方形的4根长度相等的木棒其实也可以看成是2对长度相等的木棒,那么实际上我们构造一个构造一个长方形和一个正方形所需要的长度相等的木棒对数就是4,但是同时我们还要满足一个条件,也就是我们存在4根以上同一长度的木棒。
由此我们在增减操作的过程中,维护长度相等的木棒对数,以及木棒根数在4根以上的长度种数,这两个数值即可。
长度的范围是1到1e5,首先进行一次桶排序用num[x]数组记录长度x的木棒有几根,计算初始状态下长度相等的木棒对数cas2,num[x]>=4的x的个数cas4。
之后再根据增减操作对应改变num数组和cas2,cas4的值:
+x操作:
这种情况下长度x的木棒数量是增加的,如果num[x]=3的话,增加后num[x]变为4,cas4的情况+1
如果num[x]是一个奇数的话,增加后可以多构成出一对长度相等的木棒,cas2的情况+1
-x操作:
这种情况下长度x的木棒数量是减少的,如果num[x]=4的话,减少后num[x]变为3,cas4的情况-1
如果num[x]是一个偶数的话,减少后会少构成出一对长度相等的木棒,cas2的情况-1
能构造正方形+长方形的情况为,cas4不为0,且cas2>=4
#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;
ll num[maxn];
int32_t main()
{
IOS;
int n;
cin>>n;
for(ll i=0;i<n;i++)
{
ll x;
cin>>x;
num[x]++;
}
ll cas2=0,cas4=0;
for(ll i=1;i<=100000;i++)
{
cas2+=num[i]/2;
if(num[i]>3) cas4++;
}
ll q;
cin>>q;
while(q--)
{
char c;
ll x;
cin>>c>>x;
if(c=='-')
{
if(num[x]==4) cas4--;
if(num[x]%2==0) cas2--;
num[x]--;
}
else
{
if(num[x]==3) cas4++;
if(num[x]%2) cas2++;
num[x]++;
}
if(cas4&&cas2>3) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
C题
过题用时:10min
贪心,构造,结论
给你n个数字,现在你需要将这些数字按照一定顺序排列后,使得数值相等的数字之间的间距的最小值最大。
虽然这句“最小值最大”一看就是经典的二分特色…但是实际上这道题直接贪心结论O(n)即可。
以这组数据为例:
14
1 1 1 2 2 2 3 3 3 4 4 6 7 8
我们先通过桶排序找到这些数中出现次数最多为多少次,记为Max,这个样例中Max=3。
出现次数为3的数字分别为1,2,3这三种。
在不考虑其他数字的情况下,我们采用顺序间隔摆放:1,2,3…1,2,3…1,2,3
这样的构造方式使得值相等的数字之间的距离尽可能大,距离值为Max-1=2。
注意到上面的构造中有用省略号“…”表示的空区,这个空区是我们用来摆放剩余数字的。注意到这个空区的个数为Max-1,而剩下的其他数字的出现次数最多也不会等于Max,也就是最多只能和空区的数量相同,等于Max-1,比如对于数字4出现了两次,我们继续平均摆放4到这些空区中就是了。
最终的答案为Max-1再加上每个空区中摆放的数字个数中最小的那个,因此我们要使得这些空区中的数字个数最小的那个值尽可能大,因此我们采用平均摆放的方式。
摆放4后效果为1,2,3,4…1,2,3,4…1,2,3。最终效果为1,2,3, 4,6,8 1,2,3, 4,7, 1,2,3。
记出现次数等于Max的数字个数为cas,那么除了这些数字外的数的个数等于n-Max × \times ×cas,空区的个数为Max-1。
不考虑空区的情况下,值相等的数字之间的距离为Max-1,增加空区后,答案增加的值为(n-Max × \times ×cas)/(Max-1)。
最终答案就是Max-1+(n-Max × \times ×cas)/(Max-1)。
#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;
int32_t main()
{
IOS;
int t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
vector<ll>num(n+1,0);
ll Max=0;
for(ll i=0;i<n;i++)
{
ll x;
cin>>x;
num[x]++;
Max=max(Max,num[x]);
}
ll cas=0;
for(ll i=1;i<=n;i++)
if(num[i]==Max) cas++;
cas+=(n-Max*cas)/(Max-1);
cout<<cas-1<<endl;
}
}
D题
过题用时:44min(罚时两发,白给巨难受)
dp
首先暴力搜索是必定超时的,自己算下复杂度就知道了。
我的思考方式是这样的:
对于每个位置,我们记录向上最多有几个相邻位置颜色和当前一样,向下最多又有几个相邻位置颜色和当前一样。这两个值当中较小的那个,就是我们当前位置在竖直方向上可以同时向外扩展的最大距离。
我们使用field_d[i][j](这个数据的计算方法就不需要说了吧)来记录位置(i,j)在竖直方向上同时扩展的最大距离,再通过这个数据计算向左和向右可以扩展的最大距离。
field_l[i][j]记录位置(i,j)在向左方向上最多可以伸展的距离
转移方程为field_l[i][j]=min(field_l[i][j-1]+1,field_d[i][j])。
原因在于如果要向左延伸的话,当前位置在竖直方向的延伸距离,不能小于左侧相邻位置在数值方向的延伸距离+1。(观察要求构造的图形可以得到这个结论)
field_r[i][j]记录位置(i,j)在向右方向上最多可以伸展的距离
计算方法同上
向左和向右延伸的距离中取最小,即为当前位置向外延伸的最大距离,累加到答案即可。
#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=2e3+7;
int field_u[maxn][maxn];//field_u[i][j]记录(i,j)位置向上最多有几个相邻位置和(i,j)颜色相同
int field_d[maxn][maxn];//field_u[i][j]记录(i,j)位置向下最多有几个相邻位置和(i,j)颜色相同
//最后field_d[i][j]记录的是(i,j)在竖直方向上最多可以同时延伸多少个位置,颜色都是与(i,j)相同
int field_l[maxn][maxn];
int field_r[maxn][maxn];
string s[maxn];
int32_t main()
{
IOS;
int n,m;
cin>>n>>m;
ll ans=n*m;//每个格子不向外延伸,自身构成一种答案
for(ll i=0;i<n;i++) cin>>s[i];
for(ll j=0;j<m;j++)//计算每个位置,在向上和向下两个方向最多可以延伸多少距离
{
for(ll i=1;i<n;i++)
if(s[i][j]==s[i-1][j]) field_u[i][j]=field_u[i-1][j]+1;
for(ll i=n-2;i>=0;i--)
if(s[i][j]==s[i+1][j]) field_d[i][j]=field_d[i+1][j]+1;
}
for(ll i=0;i<n;i++)//根据上面的两个数组取最小值,为竖直方向上能同时延伸
for(ll j=0;j<m;j++)
field_d[i][j]=min(field_u[i][j],field_d[i][j]);
for(ll i=0;i<n;i++)//根据竖直方向上能同时延伸的距离值,分别计算向左和向右延伸的最大距离
{
for(ll j=1;j<m;j++)
if(s[i][j]==s[i][j-1]) field_l[i][j]=min(field_l[i][j-1]+1,field_d[i][j]);
for(ll j=m-2;j>=0;j--)
if(s[i][j]==s[i][j+1]) field_r[i][j]=min(field_r[i][j+1]+1,field_d[i][j]);
}
for(ll i=0;i<n;i++)
for(ll j=0;j<m;j++)
ans+=min(field_l[i][j],field_r[i][j]);
cout<<ans<<endl;
}