题目地址:https://codeforces.com/contest/1167
同时发布在本人的洛谷博客:https://www.luogu.org/blog/996-icu/
明天线代期末考然而不怕死的我还是打算写一发题解,求线代考试RP++
G题确实没空看没空想就鸽了
A. Telephone Number
没啥好说的(虽然我第一发提交看错题目还wa了orz)
B. Lost Numbers
第一次做交互题(之前遇到过没敢写,这回正好题目简单很适合用来熟悉交互类型的题目),做法应该有很多,我是按12,23,34,45的顺序询问解答的,虽然写长了但应该还是很好看懂的。这种题目应该还是要多摸索规律
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
#define maxn 305000
int sum_12,sum_23,sum_34,sum_45;
int ans[10];
int num[10]={0,4,8,15,16,23,42};
void get_ans(int sum, int &a1, int &a2)
{
for (int i=1; i<=6; i++)
for (int j=i+1; j<=6; j++)
if (num[i]*num[j]==sum)
{
a1=num[i],a2=num[j];
return ;
}
}
int main()
{
cout<<"? 1 2"<<endl;
cin>>sum_12;
cout<<"? 2 3"<<endl;
cin>>sum_23;
cout<<"? 3 4"<<endl;
cin>>sum_34;
cout<<"? 4 5"<<endl;
cin>>sum_45;
int buf1,buf2;
get_ans(sum_12,buf1,buf2);
ans[1]=buf1,ans[2]=buf2;
get_ans(sum_23,buf1,buf2);
if (ans[2]==buf1) ans[3]=buf2;
else if (ans[2]==buf2) ans[3]=buf1;
else if (ans[1]==buf1) {swap(ans[1],ans[2]);ans[3]=buf2;}
else if (ans[1]==buf2) {swap(ans[1],ans[2]);ans[3]=buf1;}
get_ans(sum_34,buf1,buf2);
if (ans[3]==buf1) ans[4]=buf2;
else ans[4]=buf1;
get_ans(sum_45,buf1,buf2);
if (ans[4]==buf1) ans[5]=buf2;
else ans[5]=buf1;
for (int i=1; i<=5; i++)
for (int j=1; j<=6; j++)
if (ans[i]==num[j])
num[j]=0;
for (int i=1; i<=6; i++)
if (num[i])
ans[6]=num[i];
cout<<"! "<<ans[1]<<" "<<ans[2]<<" "<<ans[3]<<" "<<ans[4]<<" "<<ans[5]<<" "<<ans[6];
return 0;
}
C. News Distribution
几乎是裸题的并查集,基本操作一通乱搞即可ac,要注意的一点是对输入处理完后要对每个元素再路径压缩(即执行查询操作)一次
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
#define maxn 505000
int bcj[maxn],n,m,ans[maxn];
int find_bcj(int k)
{
return (bcj[k]==k? k: bcj[k]=find_bcj(bcj[k]));
}
int main()
{
cin>>n>>m;
for (int i=1; i<=n; i++)
bcj[i]=i;
for (int i=1; i<=m; i++)
{
int x,a,b;
cin>>x;
if (x) cin>>a;
for (int j=2; j<=x; j++)
{
cin>>b;
if (find_bcj(a)!=find_bcj(b))
bcj[find_bcj(b)]=bcj[a];
}
}
for (int i=1; i<=n; i++)
find_bcj(i);
for (int i=1; i<=n; i++)
ans[bcj[i]]++;
for (int i=1; i<=n; i++)
ans[i]=ans[bcj[i]];
for (int i=1; i<=n; i++)
cout<<ans[i]<<" ";
return 0;
}
D. Bicolored RBS
贪心,遇到的左括号与左括号之间间隔涂色,遇到的右括号与右括号之间间隔涂色,纯靠瞎猜的情况下5分钟ac该题。这种靠灵感的贪心也没啥好证明的了吧,其实也是蛮显然的
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
#define maxn 305000
int n,ind_1,ind_2;
char s[maxn];
int main()
{
cin>>n>>(s+1);
for (int i=1; i<=n; i++)
{
if (s[i]=='(')
{
cout<<ind_1%2;
ind_1++;
}
else
{
cout<<ind_2%2;
ind_2++;
}
}return 0;
}
E. Range Deleting
终于结束了前四个水题,步入正题。
其实想了蛮久的才想出近似正解,然鹅因为一些细节的处理和算法的漏洞一直wa,所以就去参考了下别人的代码,确实思路表达明确,还是要学习一个。
考虑题目中删除的方式,是由L->R的所有数字删除,这就要求删除后剩余的1->(L-1)和(R+1)->x都是非降序排列的,这就意味这删除的区间要覆盖掉所有不合法的数字。
假如我们按数字从小到大枚举删除的左端点L,那么只需要找到最小需要覆盖到的右端点R(即数字从大往小第一个不合法的端点),那么总答案就可以加上x-R+1(因为找到最小需覆盖到的删除数字R,那么L->R,L->(R+1),…L->(x-1),L->(x)区间范围内的删除就都必定是合法的,总共数量为x-R+1个),所以我们只要找到对于每个L,最小的R是多少就行了。
那么L,R的取值具体需要注意那些情况呢?
1.R不能比L大
2.R不能小于出现在L之前的最大数字
3.对给定的数列,L不能大于从左往右数第一个递减的数,R不能小于从右往左数第一个递增的数,否则必定不合法
剩下的直接参考代码即可
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
#define maxn 1005000
int n,m,a[maxn],l[maxn],r[maxn],maxv[maxn],val[maxn],pos1,posn;
ll ans;
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++)l[i]=n+1;
for(int i=1;i<=n;i++)
{
l[a[i]]=min(l[a[i]],i);//第一次出现i
r[a[i]]=max(r[a[i]],i);//最后一次出现i
maxv[i]=max(maxv[i-1],a[i]);//第i位之前最大的数
}
for(int i=1;i<=m;i++)
val[i]=max(max(maxv[r[i]],i),val[i-1]);//对1到m枚举,所存值为数列中数字i之前出现的最大数(或i本身)
int nowr=0;
for(int i=1;i<=m;i++)
{
if(l[i]<nowr)
{
pos1=i-1;//从左往右第一个不合法数前一个数
break;
}
else nowr=max(nowr,r[i]);
}
if(!pos1)pos1=m;
int nowl=n+1;
for(int i=m;i>=1;i--)
{
if(r[i]>nowl)
{
posn=i+1;//从右往左第一个不合法数后一个数
break;
}
else nowl=min(nowl,l[i]);
}
if(!posn)posn=1;
for(int i=1;i-1<=pos1;i++)
{
int p=max(posn-1,val[i-1]);
p=max(p,i);
ans+=m-p+1;
}
cout<<ans<<endl;;
return 0;
}
F. Scalar Queries
这类计算所有 下标为L->R(1
例如对数列 a1,a2,a3…a(n-1),a(n) 计算所有 L->R区间内数字和 的和,若是暴力相加则复杂度是O(n^2)的,可以考虑每个数对答案贡献的次数,a1总共会被取到1*n次,a2总共会被取到2*(n-1)次,a3总共会被取到3*(n-2)次…最终答案只要把 a1*1*n + a2*2*(n-1) …逐项相加起来即可O(n)的得到最终答案。
再回到该题,设一个数字ai在数列中的位置是i,若ai是最小的数,则会对总答案产生贡献的次数为i*(n-i+1) (即为包括该数的所有区间的数目)。若该数左侧有比ai小的数aj,aj在数列中的位置为j,则这个数会使ai对答案产生贡献的次数加上j*(n-i+1)(即为既包括ai又包括aj的所有区间数),右侧有比ai小的数则同理。
如果我们把数字按大小排序好(当然在数列中的原位置要记录),然后从小到大进行上述计算,考虑更小的数的情况之时就只要取之前所有考虑的数,这个就很容易让人想起使用树状数组的结构,当然线段树也行,维护两个树状数组,一个专门用来处理较小数在左侧情况,一个专门用来处理较小数在右情况(分两个的原因主要是因为即使是同一个较小数,其位置在当前考虑的数的左侧和在右侧对当前数产生的贡献的数也是不同的)
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
#define maxn 505000
ll tr_1[maxn+10],tr_2[maxn+10],mod=1e9+7;
ll ans,n;
struct str
{
ll x,id;
}num[maxn];
bool cmp (str a, str b)
{
return a.x<b.x;
}
ll query(ll *tr, ll x)
{
ll res=0;
while (x)
res=(res+tr[x])%mod,x-=x&-x;
return res;
}
void add(ll *tr, ll x, ll v)
{
while (x<=n)
tr[x]=(tr[x]+v)%mod, x+=x&-x;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for (ll i=1; i<=n; num[i].id=i,i++)
cin>>num[i].x;
sort(num+1,num+1+n,cmp);
for (ll i=1; i<=n; i++)
{
ll w=num[i].id, temp=0;
temp= (temp+(w)*(n-w+1))%mod;//加上自身贡献次数
temp= (temp+(n-w+1)*query(tr_1, w-1))%mod;//加上左侧较小数产生的贡献次数
temp= (temp+(w)*(query(tr_2,n)-query(tr_2,w)+mod))%mod;//加上右侧较小数产生贡献次数
ans= (ans + (temp*num[i].x)%mod)%mod;//贡献次数乘以当前考虑的数的值加到总答案中
add(tr_1,w,w),add(tr_2,w,n-w+1);//把当前数分别作为左侧较小数和右侧较小数插入到树状数组中
}
cout<<ans<<endl;
return 0;
}