Educational Codeforces Round 94(Div。2の評価)AE問題解決策
//評価値2075/2184で記述
// 3番目の試験が終了すると、通常のトレーニング
が再開されます。// 次のcfが
Eとこれを再生し始めます少し前に書いた質問はまったく同じです...
コンテストリンク:https://codeforces.com/contest/1400
質問の
単純な構造
0と1のみを含む長さ2n-1の文字列aが与えられた場合、長さnの文字列bを作成する必要があります。これは、長さnの任意の連続部分文字列aを満たし、少なくとも1つの位置があります。上記の数字は同じです。
文字列aの長さは2n-1であり、中央のn番目の文字に特別な位置があることに注意してください。この文字は、長さnの文字列aの連続する部分文字列に出現します。
これらの連続する部分文字列におけるこの文字の位置は、nの位置1〜nを満たしています。
長さnの文字列を直接作成します。これらはすべて文字列のn番目の文字です。
#include<bits/stdc++.h>
#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--)
{
int n;
cin>>n;
string s;
cin>>s;
char c=s[n-1];
for(int i=0;i<n;i++) cout<<c;
cout<<endl;
}
}
質問B
貪欲、暴力
タイトルは、2人がいて、それぞれバックパックの容量があることを意味します。これで、サイズsのcnts swordとサイズwのcntw axesがあります。次に、2つが持つことができる武器の数を確認します。
まず、s <= w、つまり剣の重さが斧よりも大きくないことを前提に、貪欲なプロセスを実行します。この時点では、できるだけ多くの剣を使用する必要があります。
証明は次のとおりです。
特定のスキームの場合、剣は奪われず、斧が奪われます。剣の重さは斧より重くないので、私たちの斧はすべて同数の剣で置き換えることができ、そのための余地があります。
これは、貪欲に剣を好むことが正しいことを証明しています。
次に、剣と軸の数が2e5以内であることに注意してください。最初の人が力で取った剣の数を数え、列挙ごとに貪欲なプロセスを実行できます。
#include<bits/stdc++.h>
#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 p,f;
ll cnts,cntw,s,w;
cin>>p>>f>>cnts>>cntw>>s>>w;
ll ans=0;
if(s>w) swap(cnts,cntw),swap(s,w);
for(ll i=0;i*s<=p&&i<=cnts;i++)
{
ll first_s,first_w,second_s,second_w;//first代表第一个人拿的道具,s代表剑,w代表斧头
first_s=i;//第一个人拿走i把剑
second_s=min(f/s,cnts-first_s);//第二个人尽可能多拿剑,但是拿的个数不能超过剩下的剑的数量
first_w=min((p-first_s*s)/w,cntw);//第一个人剩余的背包容量,尽可能多得拿斧头
second_w=min((f-second_s*s)/w,cntw-first_w);//第二个人接着拿剩下的斧头,注意背包容量是剩余的,斧头个数也是剩余的
ans=max(ans,first_s+first_w+second_s+second_w);
}
cout<<ans<<endl;
}
}
質問Cの
構造、実装
質問は、0と1のみを含む長さnの文字列aと距離値xが与えられた場合、0と1のみを含む長さnの文字列bを作成して、文字列aが文字からなるようにする必要があることを意味します文字列bは、次の規則に従って取得されます。文字列a
のi番目の文字について、i + xまたはixの添え字が1-nの間にある場合、文字列bのi + xまたはix番目の文字文字は1であり、文字列aのi番目の文字も1でなければならず、それ以外の場合は0です。
まず第一に、xとnの間の値の関係を考えると、任意のiについて、i + xとixの少なくとも1つが1-nの範囲にあることがわかります。
a [i]の場合:
a [i] = 0の場合、b [ix]およびb [i + x]も0でなければなりません。a
[i] = 1の場合、b [ix]およびb [i + x]それらの少なくとも1つが1の場合
次は建設プロセスです。建設方法を考える必要があります。現在の建設位置は、以前に建設した位置に影響しなくなります。
この質問の特別なルールに注意してください。a [i]の場合、気にする必要があるのはb [ix]とb [i + x]の2つの位置だけです。
i1 <i2、i1 + x、i2 + xなどの異なるi値の場合、i1-xとi2-xも等しくなく、i1 + xとi2-xのみが等しい場合があります。
aの添え字iに応じて、対応するixとi + xをbで構築します。
構築プロセスでは、現在の位置iについて、前回の構築プロセスではb [i + x]が構築されておらず、b [ix]が構築されている可能性があります。
a [i] = 1の場合、b [ix]とb [i + x]に1があるかどうか、1がない場合、および1を構築するために構築する位置があるかどうかを確認する必要があります。目標を作成します。構築する位置がある場合、b [ix]を構築できる場合、b [ix]を1として構築しますが、b [i + x]はa [i + 2xに影響するため、構築してから構築することはできません。 ]インパクトがあり、私たちは貪欲な戦略を採用して左側を最初に構築する必要があります。
a [i] = 0の場合、b [ix]とb [i + x]が両方とも0かどうかを確認し、これら2つの位置の両方を0に構成する必要があります。これらが満たされない場合、ターゲットを構成できません。
この構造アイデアに従って実装できます。
#include<bits/stdc++.h>
#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--)
{
string s1,s2;
cin>>s1;
int n=(int)s1.size();
s2.resize(n,'!');//s2为我们构造的字符串,一开始全部置为字符'!'代表该位置为待构造
int x;
cin>>x;
bool flag=1;//指示目标字符串能否被构造,1代表可以
for(int i=0;i<n;i++)//s1字符串中按照下标从小到大依次扫过去
{
if(s1[i]=='0')//当s1[i]=0的时候,s2[i-x]和s2[i+x]必须都为0
{
//由于s2[i+x]不可能在前面的构造过程中被构造过,因此第一个判断可行性的if里面没考虑
if(i-x>=0&&s2[i-x]=='1') flag=0;//如果s2[i-x]在之前的构造过程中被构造了1,那么与当前条件矛盾
if(i-x>=0) s2[i-x]='0';//构造左右两个位置为0
if(i+x<n) s2[i+x]='0';
}
else
{
bool f=0;
if(i-x>=0&&s2[i-x]=='1') f=1;//检测左右位置是否已经存在1
if(!f)//如果不存在则需进行构造1
{
if(i-x>=0&&s2[i-x]=='!') s2[i-x]='1';//优先构造左侧
else if(i+x<n) s2[i+x]='1';//构造右侧
else flag=0;//两侧都无法构造,则产生矛盾,目标字符串无法构造
}
}
}
for(int i=max(n-x,0);i<n;i++) if(s2[i]=='!') s2[i]='0';
//注意上述构造过程结束后,目标字符串中的末尾部分是可能存在未构造部分的,任意构造均可
//具体原因不表,思考上述构造过程即可
if(flag) cout<<s2<<endl;
else cout<<-1<<endl;
}
}
質問D
暴力、接頭辞、
長さnのシーケンスa(n <= 3000)が与えられた場合、1 <= i <j <k <l <= n、およびa [を満たすペア(i、j、k、l)の数を計算する必要があります。 i] = a [k]、a [j] = a [l]。
注意到这道题的n非常小,所以我们可以采取一些相对比较暴力的做法。
这里由于有两对相等数字,四个下标,因此我们至少要找两个下标作为初始固定位置(时间复杂度消耗为n2,1e6级别)。
如果我们固定的是i和j,或者k和l的话(也就是固定两对相等数字的左侧,或者右侧),以固定i和j为例,我们接着考虑k和l满足的有几种,会发现此时非常困难,因为我们既要考虑到a[i]=a[k],a[j]=a[l],还要考虑k<l,而此时k和l的限制都是大于j,难以计算情况数量。
接着我们考虑其中一对相等数字取作左侧下标,另一对相等数字取右侧下标。我们考虑固定j和k,再去找i和l。此时i的位置一定是小于j,l的位置一定是大于k,由于j<k,也就是意味着满足上述条件时,i一定是小于l的。
我们可以用sum[i][j]记录数列前i个数字中(由于n特别小,时空复杂度均为1e6而已毫无问题),数字j出现了几次,也就是前缀和数组。当我们固定了下标j和k后,我们使用前缀和数组查询下标小于j的数字中等于a[k]的有几个,记为x,再查询下标大于k的数字中等于a[j]的有几个,记为y。左右两部分,两两任意配对均为一种答案,因此累加的答案即为x × \times × y。
由此,我们预处理出前缀和数组sum(1e6复杂度),再暴力枚举j和k的下标(1e6复杂度),使用前缀和数组计算每种情况下(O(1)),i和l有多少种方案累加到ans即可。
#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=3e3+7;
int n;
int num[maxn];
int sum[maxn][maxn];//前缀和数组,sum[i][j]记录num数列中的前i个数字中,j出现了几次
int32_t main()
{
IOS;
int t;
cin>>t;
while(t--)
{
for(int i=0;i<n;i++)
for(int j=1;j<=n;j++) sum[i][j]=0;//不要用memset,避免tle,清理上次循环用过的部分即可
ll ans=0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>num[i];
if(i)
for(int j=1;j<=n;j++)//先继承前i-1个数字中,各个数字的出现情况
sum[i][j]=sum[i-1][j];
sum[i][num[i]]++;//当前位置的数字个数+1
}
for(int j=1;j<n;j++)
for(int k=j+1;k<n;k++)
{
int i=sum[j-1][num[k]];//i的位置需要满足小于j,且对应数字等于num[k]
int l=sum[n-1][num[j]]-sum[k][num[j]];//l的位置需要满足大于k,且对应数字等于num[j]
ans+=i*l;
}
cout<<ans<<endl;
}
}
E题
贪心,结论,分治,dfs
//前段时间集训刚写过一模一样的题…没记错应该也是cf上的
問題は、自然数のみを含む長さnの数列(n <= 5000)が与えられた場合、2つの操作方法があることを意味します。1つ
目は、特定の数を選択してこの数を0に変更することです。
2つ目は、各数値が-1になるように、各数値をゼロ以外にする必要がある連続領域を選択することです。
次に、少なくともシーケンス全体を0に設定するために必要な操作の数を尋ねます。
長さnのシーケンスの場合、2番目の操作を行わず、最初の操作のみを使用すると、必要な操作の数はnになります。
次に、最初の操作を使用しながら2番目の操作を使用することを検討してください。
Minを現在のシーケンスの最小数として定義します。これは、1-nのシーケンス全体に対して最初の操作を使用できる最大回数でもあります。
まず、貪欲なプロセスでは、2番目の操作を実行するたびに、できるだけ長い部分を選択する必要があります。そうしないと、同じか、より良い解決策があります。
その後、1-nのシーケンス全体で2番目の操作を数回使用する必要があります。ここでの思考プロセスでは、2番目の操作をx回実行するために、x <Minです。これは、2番目の操作を続行する余地がまだあることを意味します。この時点では、最初の操作のみを実行します。シーケンスの各数値はMin以上であるので、各数値はこの時点では0ではありません。最初の操作をn回実行する必要があります。
この時点での操作の総数はx + nであり、これは各位置で最初の操作を直接実行する操作の総数xよりも多いです。
これから、もう1つの貪欲な結論が得られます。長さnのシーケンスの最小値はMinです。最初の操作をn回実行した後、または2番目の操作をMin回実行した後、上記の操作をループで実行し続けます。2回目の操作を最小回数実行した後、元のシーケンスはいくつかの連続する非ゼロサブシーケンスに分割され、上記のプロセスは各連続する非ゼロサブシーケンスに対して再帰的に実行できます。
#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define llINF 9223372036854775807
using namespace std;
ll n;
vector<ll>num;
ll dfs(vector<ll>now)
{
ll temp=0,len=now.size();//temp为对整个数列now使用Min次第一种操作后,对剩余部分清零还需要的操作次数
ll Min=llINF;
for(ll i=0;i<len;i++)
Min=min(Min,now[i]);
vector<ll>next;
for(ll i=0;i<len;i++)
{
if(now[i]==Min)
{
if(next.size())
{
temp+=dfs(next);//递归计算进行Min次操作后,剩下的连续非0部分还需多少次最少操作
next.clear();
}
}
else next.push_back(now[i]-Min);
}
if(next.size()) temp+=dfs(next);
return min(len,Min+temp);//len为只进行第二种操作的操作次数,即为数列now长度
}
int32_t main()
{
IOS;
cin>>n;
num.resize(n);
for(auto &x:num) cin>>x;
cout<<min(n,dfs(num))<<endl;//原数列不一定是连续的非0序列,但是这种写法的dfs是可以处理这种情况的
}