%你赛题解
对于\(60\)分的数据 , 这个范围内的数据是纯随机的,而且不会有什么卡人的地方(因为随机的话并随机不出来\(happiness\)这个单词)。这些分数大概就是写挂了的同学得到的分数。
对于另外\(40\)分的数据 , 感谢由\(wucstdio\)亲自手造数据,这个范围内的数据卡了很多的东西.
说了这么多,我们开始考虑正解怎么写。
首先题面的意思就是,如果你不能在交换两个字符后使得\(happiness\)不是原字符串的字串,就输出\(No\),那么我们可以很显然的想到,对于交换两个字符后,最多使得2个\(happiness\)从原字符串中消失。那么,我们可以做一遍\(KMP\)(因为数据范围较小,暴力模拟也可以)跑出在原来的字符串中有多少个\(happiness\)。所以,如果最后你发现\(happiness\)的个数\(num\)比\(2\)要大,那么无论你怎么换,最后一定会有一个或者多个\(happiness\)出现在原来的字符串中,对于这种情况,我们可以直接输出\(No\)。
然后我们来考虑\(num <= 2\)的情况 ,当\(num = =2\)时,很显然的,我们需要在这两个\(happiness\)中交换。但是需要注意一点,我们必须保证交换之后的字符串中不出现新的\(happiness\)这个单词,所以当我们枚举交换哪一位的时候,我们需要考虑一下交换之后的前缀和后缀,之后再交换就可以了。
当\(num == 1\)的情况时,交换的方法和当\(num == 2\)的时候并没有多大区别。
所以我们来考虑一下当\(num==0\)的时候的情况 , 在\(num==0\)的情况时,我们只需要保证在交换之后不会出现新的\(happiness\)这个单词即可。(注意换的时候考虑一下前缀和后缀就好了)**
激动人心的代码时间:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
char s[N];
char h[10] = { '0' , 'h' , 'a' , 'p' , 'p' , 'i' , 'n' , 'e' , 's' , 's' };
int num , start[N];
int main ( void ) {
scanf ( "%s" , s + 1 );
int len = strlen ( s + 1 );
int now = 1;
for ( int i = 1 ; i <= len ; i++ ) {
if ( s[i] == h[now] ) {
if ( now == 9 ) {
num++;
start[num] = i - 9 + 1;
now = 1;
continue;
}
else {
now++;
continue;
}
}
else if ( s[i] != h[now] ) {
now = 1;
if(s[i]==h[now])now++;
continue;
}
}
if ( num >= 3 ) {
puts ( "NO" );
return 0;
}
else {
if ( num == 2 ) {
puts ( "YES" );
printf ( "%d %d\n" , start[1] + 1 , start[2] + 2 );
return 0;
}
if ( num == 1 ) {
puts ( "YES" );
printf ( "%d %d\n" , start[1] + 3 , start[1] + 4 );
return 0;
}
if ( num == 0 ) {
if ( s[1] != 'h' && s[1] != 'a' ) {
puts ( "YES" );
printf ( "%d %d\n" , 1 , 2 );
return 0;
}
else {
if ( s[1] == 'h' ) {
for ( int i = 2 ; i <= len ; i++ ) {
if ( s[i + 1] != 'a' ) {
puts ( "YES" );
printf ( "%d %d\n" , 1 , i );
return 0;
}
}
}
else if ( s[1] == 'a' ) {
for ( int i = 2 ; i <= len ; i++ ) {
if ( s[i + 1] != 'p' ) {
puts ( "YES" );
printf ( "%d %d\n" , 1 , i );
return 0;
}
}
}
}
}
}
return 0;
}
$ T2 $ . (亚运会):
我才不会告诉你这道题本来是一道大毒瘤模拟然后前几天才改的题呢
题面写的应该挺清楚的,我在这里就不重新讲一遍了。
首先对于\(30\)分的数据,\(n , m <=1000\)直接\(n\)遍\(Spfa \ or \ Dijkstra\)判断联通性,没了
然后考虑\(60\)分的数据,象征性的给一些常数写的大的同学,没有什么实际意义
直接考虑正解:
我们可以很显然的发现,对于在这一张图中的点 , 如果它们在一个强连通分量中 , 那么一定可以相互到达 , 所以我们可以对这张图进行\(Tarjan\)缩点,然后考虑缩点后的新图。我们知道,缩点之后的一张图,一定是一个\(DAG\)。那么我们考虑一下这张图上的\(Topsort\)。我们在\(Topsort\)的过程中,如果发现了同时有两个点的入度为零,那么我们直接输出\(No\).
为什么?
我们知道,在\(Topsort\)的过程中,如果同时出现了两个入度为\(0\)的点,那么这两个点之间一定是不可以相互到达的。因为一个点在跑完它自己的\(Topsort\)后并不会经过另一个入度为\(0\)的点。那么这就是这道题的依据
代码时间:
#include <cstdio>
#include <cstring>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
struct Edge{
int s;
int e;
int next;
}edge1[N],edge2[N];
stack <int> st;
int n,m,e_num1,e_num2,vis_num,cnt;
int head[N],instack[N],low[N],tim[N],belong[N],de[N];
inline int Read () {
int s = 0 , w = 1;
char ch = getchar ();
while ( ch > '9' || ch < '0' ) { if ( ch == '-' ) w = -1; ch = getchar ();}
while ( ch >='0' && ch <='9' ) { s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void add(int a,int b,Edge edge[],int &e_num){
edge[e_num].s=a;
edge[e_num].e=b;
edge[e_num].next=head[a];
head[a]=e_num++;
return;
}
void tarjan(int x){
int j;
tim[x]=low[x]=++vis_num;
instack[x]=1;
st.push(x);
for(j=head[x];j!=-1;j=edge1[j].next){
int u=edge1[j].e;
if(tim[u]==-1){
tarjan(u);
if(low[x]>low[u])low[x]=low[u];
}
else if(instack[u] && low[x]>tim[u])low[x]=tim[u];
}
if(low[x]==tim[x]){
cnt++;
do{
j=st.top();
st.pop();
instack[j]=0;
belong[j]=cnt;
}while(j!=x);
}
return;
}
int topo(){
int i,cur,u,count,num;
count=0;
for(i=1;i<=cnt;i++){
if(de[i]==0){
cur=i;count++;
}
}
if(count>1)return 0;
num=cnt;
while(num--){
count=0;
for(i=head[cur];i!=-1;i=edge2[i].next){
u=edge2[i].e;
de[u]--;
if(de[u]==0){
count++;cur=u;
}
}
if(count>1)return 0;
}
return 1;
}
void solve(){
int i;
cnt=vis_num=0;
memset(instack,0,sizeof(instack));
memset(low,0,sizeof(low));
memset(tim,-1,sizeof(tim));
for(i=1;i<=n;i++){
if(tim[i]==-1)
tarjan(i);
}
e_num2=0;
memset(head,-1,sizeof(head));
memset(de,0,sizeof(de));
for(i=0;i<e_num1;i++){
int j=edge1[i].s;
int k=edge1[i].e;
if(belong[j]!=belong[k]){
add(belong[j],belong[k],edge2,e_num2);
de[belong[k]]++;
}
}
if ( topo() == 1 ) {
puts ("Yes");
return;
}
else puts("No");
return;
}
int main( void ){
freopen ( "game.in" , "r" , stdin );
freopen ( "game.out" , "w" , stdout );
int t;
scanf("%d",&t);
while(t--){
int a,b;
n = Read();
m = Read();
e_num1=0;
memset(head,-1,sizeof(head));
while(m--){
a = Read();
b = Read();
add(a,b,edge1,e_num1);
}
solve();
}
return 0;
}
$ T3 $ . (我们仍未知道我们那天所见过的花的名字):
吐槽时间:这真的不是 $ lxl $ 出的题 $ QAQ $
我估计这道题唯一的坑点就是误导一些 $ dalao $ 写带修主席树了
对于 $ 30 $ 分的数据,我们直接暴力模拟就好了。
对于 $ 60 $ 分的数据,我本来没想开这么大的,但是不然就有常数优化大师可以暴力碾标算了。这60分是给莫队的分数,其实也算是给常数写大了的同学们的。(莫队讲起来太麻烦,就不讲了QAQ)
对于 $ 100 $ 分的数据,我们可以发发现数据范围下边有这样一行人畜无害的小字:
对于所有的数据,数据保证不同的k的个数不超过10个
emmmmm....看到这一句话应该就知道正解绝对不会是主席树了。
首先考虑操作一:维护区间最小值 $ + $ 边界插入。大家应该都会QWQ。。
(动态开点不会的话。。。。。)告诉我我再讲一下吧
然后就是操作二: 维护每个区间内有多少数的小于这个数。嗯,主席树板子(逃
那我给泥萌那个提示干嘛啊。。。。。
其实,因为 $ k $ 的个数很小,所以我们直接可以考虑建立 $ k $ 的个数棵线段树,每个线段树维护离散化之后相应的那个k值,然后区间求和。
维护的话,因为只修改边界,而且是单点修改,所以直接动态开点的方法修改就好了。
没了
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#define LC ( ( root ) << 1 )
#define RC ( ( root ) << 1 | 1 )
using namespace std;
const int N = 2e5 + 10;
int n , q , cnt1 , cnt2;
int num[N];
struct Que {
int sign;
int left;
int right;
int k;
}que[N];
struct Tree {
int size;
int val;
}tree[N << 3][12];
map < int , bool > mp;
map < int , int > mp2;
map < int , int > mp3;
inline int read () {
int s = 0 , w = 1;
char ch = getchar ();
while ( ch > '9' || ch < '0' ) { if ( ch == '-' ) w = -1; ch = getchar ();}
while ( ch >= '0' && ch <= '9' ) { s = s * 10 + ch - '0'; ch = getchar ();}
return s * w;
}
inline int min ( int x , int y ) {
return x < y ? x : y;
}
void Build1 ( int root , int start , int end ) {
tree[root][0].size = end - start + 1;
if ( start == end ) {
tree[root][0].val = num[start];
return;
}
int mid = ( ( start + end ) >> 1 );
Build1 ( LC , start , mid );
Build1 ( RC , mid + 1 , end );
tree[root][0].val = min ( tree[LC][0].val , tree[RC][0].val );
return;
}
void Build2 ( int root , int start , int end , int sign , int kkk ) {
tree[root][sign].size = end - start + 1;
if ( start == end ) {
tree[root][sign].val = ( kkk <= num[start] ) ? 1 : 0;
return;
}
int mid = ( ( start + end ) >> 1 );
Build2 ( LC , start , mid , sign , kkk );
Build2 ( RC , mid + 1 , end , sign , kkk );
tree[root][sign].val = tree[LC][sign].val + tree[RC][sign].val;
return;
}
void update ( int root , int nstart , int nend , int qstart , int qend , int sign , int addval ) {
if ( qstart > nend || qend < nstart )
return;
if ( qstart == nstart && qend == nend ) {
if ( !sign ) {
tree[root][0].val = addval;
num[nstart] = addval;
}
else {
if ( tree[root][0].val >= mp3[sign] )
tree[root][sign].val = 1;
}
return;
}
int mid = ( ( nstart + nend ) >> 1 );
update ( LC , nstart , mid , qstart , qend , sign , addval );
update ( RC , mid + 1 , nend , qstart , qend , sign , addval );
if ( !sign )
tree[root][0].val = min ( tree[LC][0].val , tree[RC][0].val );
else
tree[root][sign].val = tree[LC][sign].val + tree[RC][sign].val;
return;
}
int check ( int root , int nstart , int nend , int qstart , int qend , int sign ) {
if ( qstart > nend || qend < nstart ) {
if ( !sign ) return 0x3f3f3f3f;
else return 0;
}
if ( qstart <= nstart && qend >= nend )
return tree[root][sign].val;
int mid = ( ( nstart + nend ) >> 1 );
if ( !sign )
return min ( check ( LC , nstart , mid , qstart , qend , sign ) , check ( RC , mid + 1 , nend , qstart , qend , sign ) );
else
return check ( LC , nstart , mid , qstart , qend , sign ) + check ( RC , mid + 1 , nend , qstart , qend , sign );
}
int main ( void ) {
freopen ( "question.in" , "r" , stdin );
freopen ( "question.out" , "w" , stdout );
memset ( num , 0x3f3f3f3f , sizeof ( num ) );
n = read () , q = read ();
for ( int i = 1 ; i <= n ; i++ ){
num[i] = read ();
}
Build1 ( 1 , 1 , n + q + 1 );
for ( int i = 1 ; i <= q ; i++ ) {
que[i].sign = read ();
if ( que[i].sign == 1 ) {
que[i].left = read ();
que[i].right = read ();
continue;
}
else if ( que[i].sign == 2 ) {
que[i].left = read ();
que[i].right = read ();
que[i].k = read ();
if ( !mp[que[i].k] ) {
cnt1++;
mp[que[i].k] = 1;
mp2[que[i].k] = cnt1;
mp3[cnt1] = que[i].k;
Build2 ( 1 , 1 , n + q + 1 , cnt1 , que[i].k );
}
}
}
int now_right = n;
for ( int i = 1 ; i <= q ; i++ ) {
int opts = que[i].sign;
if ( opts == 1 ) {
int l = que[i].left;
int r = que[i].right;
int ans = check ( 1 , 1 , n + q + 1 , l , r , 0 );
printf ( "%d\n" , ans );
now_right++;
for ( int j = 0 ; j <= cnt1 ; j++ )
update ( 1 , 1 , n + q + 1 , now_right , now_right , j , ans );
// for ( int rr = 1 ; rr <= now_right ; rr++ )
// cout << check ( 1 , 1 , n + q + 1 , rr , rr , 0 )<< " ";
// cout << endl;
}
else {
int l = que[i].left;
int r = que[i].right;
int kkkk = que[i].k;
int ans = check ( 1 , 1 , n + q + 1 , l , r , mp2[kkkk] );
printf ( "%d\n" , ans );
now_right++;
for ( int j = 0 ; j <= cnt1 ; j++ )
update ( 1 , 1 , n + q + 1 , now_right , now_right , j , ans );
// for ( int rr = 1 ; rr <= now_right ; rr++ )
// cout << check ( 1 , 1 , n + q + 1 , rr , rr , 0 )<< " ";
// cout << endl;
}
}
return 0;
}
三道题讲完了,我感觉我还是不毒瘤的吧,,,
题目挺简单的QWQ