EOJ 2020.1月賽 部分題解和總結

感謝EOJ網站沒承住一千人的壓力,增加了半個小時的比賽時間,使得我成功三題,苟了一個rk10。B題感覺屬實太難,就不補了。賽後補了道D題。

A. 迴文時間

題意:問從2020年1月22日的10:02:02之後第k個迴文時間串是什麼。迴文時間串的定義是年月日小時分鐘秒組成的字符串爲迴文串。起始時間的時間串爲(20200122100202)符合迴文時間串的定義。
題解:暴力就完事了。觀察到因爲是迴文串,所以只要暴力年月日就可以確定後面的時分秒,check一下是否合法就行。
eg:某次從學校對面網吧回宿舍,一個人走在宿舍的路上,我也想到了這道題,後來隨便腦補了一個暴力法就可以過了。結果碰到了一樣的題之後卻因爲前兩發沒有寫十一月的日期白白wa了。

//太暴躁了 寫得好醜
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
int k,f[30],ans=-1;
char s[10];
bool flag(int x){
	if(((x%4==0) && (x%100!=0)) || x%400==0)return true;
	return false;
} 
bool check(int x,int y,int z){
	if(z%10!=z/10)return false;
	if(y%10*10+y/10>=24)return false;
	if(x/1000+(x-(x/1000)*1000)/100*10>=60)return false;
	if(x%10*10+x%100/10>=60)return false;
	return true;
}
int main(){
	//freopen("in.txt","r",stdin);
	//freopen("1.txt","w",stdout);
	cin>>k;
	f[1]=31;f[3]=31;f[5]=31;f[7]=31;f[8]=31;f[10]=31;f[12]=31;
	f[4]=30;f[6]=30;f[9]=30;f[11]=30;
	for(int i=2020;i<=9999;i++)
		for(int j=1;j<=12;j++){
			if(flag(i))f[2]=29;
			else f[2]=28;
			int st=1;
			if(i==2020 && j==1)st=22;
			for(int l=st;l<=f[j];l++){
				if(check(i,j,l))ans++;
				if(ans==k){
					printf("%d",i);
					if(j/10==0)printf("0%d",j);
					else printf("%d",j);
					if(l/10==0)printf("0%d",l);
					else printf("%d",l);
					int t=j;
					for(int ii=0;ii<2;ii++){
						s[ii]=t%10+'0';
						t/=10;
						cout<<s[ii]; 
					}
					t=i;
					for(int ii=0;ii<4;ii++){
						s[ii]=t%10+'0';
						t/=10;
						cout<<s[ii];
					}

					cout<<endl;
					return 0;
				}
			}
		}	 
	return 0;
}

E. 數的變幻

題意:給出一個正整數xx,如果是奇數變成x1x-1,如果是偶數變成x2\frac {x} {2},直到xx變成1。這個過程會形成一個序列。從1~n會有n個不同的序列,其中數字也會重複的出現。問出現k次的最大的數字是什麼數字。
題解:觀察發現,奇數和偶數分別滿足單調性。所以考慮對奇數和偶數分別二分,取兩個二分之後較大的值。對於check部分,假設xx是已經被操作過的數,可以發現[2x,2x+1][2*x,2*x+1]都是可以到達xx的。我們反向累加上去,如果總和大於等於k,那麼答案合法,否則不合法。注意處理一下邊界即可
eg:某次cf原題,感謝czb聚聚的傾囊相授,不然肯定不會出的這麼快。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
ll n,k;
int check(ll x){
	ll l=x,r=x;
	if(x%2==0)r++;
	ll ans=0;
	while(l<=n){
		ans+=min(n,r)-l+1;
		l*=2;r=r*2+1;
	}
	return ans>=k;
}
int main(){
	int T;
	cin>>T;
	while(T--){
		scanf("%lld%lld",&n,&k);
		ll l=1,r=(n+1)/2,ans=0,mid;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(2*mid-1))l=mid+1,ans=2*mid-1;
			else r=mid-1;
		}
		l=1,r=n/2;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(2*mid))l=mid+1,ans=max(ans,2*mid);
			else r=mid-1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

C. 最簡邊集

題意:給出一個有向無環圖,在保持連通性的情況下儘量刪邊,問最多能刪多少邊。
題解:有向無環圖,大力拓撲排序。按照拓撲序反向加邊,如果不改變聯通性就不加邊。用bitset直接相或,可以維護聯通性。
eg:bzoj原題,一開始wa3,重構之後tle21,加上了玄學優化們,才終於過了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e4+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
int n,m,cnt,num,ans;
bitset<maxn>f[maxn];
struct arr{
	int next,to;
}edge[50010];
int head[maxn],in[maxn],a[maxn],b[maxn],to[maxn];
inline bool cmp(int x,int y){
	return b[x]<b[y];
}
inline void add(int x,int y){
	cnt++;
	edge[cnt].to=y;edge[cnt].next=head[x];
	head[x]=cnt;
}
inline void topusort(){
	queue<int>q;
	for(int i=1;i<=n;i++)
		if(!in[i])q.push(i);
	while(!q.empty()){
		int x=q.front();q.pop();
		a[++num]=x;//第幾個入隊的是誰 
		b[x]=num;//誰第幾個入隊 
		for(int i=head[x];i!=-1;i=edge[i].next){
			in[edge[i].to]--;
			if(!in[edge[i].to])q.push(edge[i].to);
		}
	}
}
inline void solve(){
	for(int i=n;i>=1;i--){
		int x=a[i],tot=0;
		f[x][x]=1;
		for(int j=head[x];j!=-1;j=edge[j].next){
			to[++tot]=edge[j].to;
		}
		sort(to+1,to+1+tot,cmp);
		for(int j=1;j<=tot;j++)
			if(f[x][to[j]])ans++;
			else f[x]|=f[to[j]];
	}
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		memset(head,-1,sizeof(head));
		cnt=0;num=0;ans=0;
		n=read();m=read();
		for(int i=1;i<=n;i++)f[i].reset();
		for(int i=1;i<=m;i++){
			int x,y;
			x=read();y=read();
			add(x,y);
			in[y]++;
		}
		topusort();
		solve();
		printf("%d\n",ans);
	}
	return 0;
}

D. 對稱變量

題意:給出一個n元二次多項式,其中有m個二次項(係數都爲1且都是由乘積構成),可能存在平方項。對稱變量的定義是,交換兩個變量的所有位置,如果還是等價的則是一對對稱變量。問有多少對對稱變量。
題解:考慮兩個變量,有兩種情況。一、兩變量之間有乘積項,則兩變量的哈希值去掉對方所表示的哈希值之後應該相等。二、兩變量之間沒有乘積項,則其對應的哈希值應該相等。此外,含平方項的應該單獨處理。
eg:比賽的時候以爲是一道圖論題,賽後問了學弟才發現是哈希。哈希還是值得好好研究研究啊QAQ。

#include "stdio.h"
#include "string.h"
#include "iostream"
#include "algorithm"
#include "vector"
#include "math.h"
#include "map"
#include "set"
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
const int INF=0x3f3f3f;
const int mod=1e9+7;
int n,m;
ll h[maxn];
int x[maxn],y[maxn];
struct arr{
    ll h;
    bool flag;
}a[maxn],b[maxn];
void gethash(){
    h[0]=233;
    for(int i=1;i<maxn;i++)h[i]=h[i-1]*1003;
}
bool cmp(arr a,arr b){
    return a.h<b.h;
}
int main(){
    gethash();
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++)a[i].h=0,b[i].h=0;
        ll ans=0;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x[i],&y[i]);
            a[x[i]].h+=h[y[i]];
            if(x[i]==y[i])a[x[i]].flag=1;
            else a[y[i]].h+=h[x[i]];
        }
        for(int i=1;i<=m;i++)
            if(x[i]==y[i])continue;
            else if(a[x[i]].h-h[y[i]]==a[y[i]].h-h[x[i]])ans++;
        int pos=0;ll sum=1;
        for(int i=1;i<=n;i++)
            if(a[i].flag)b[++pos].h=a[i].h-h[i];
        sort(a+1,a+1+n,cmp);
        for(int i=2;i<=n;i++)
            if(a[i].h==a[i-1].h)sum++;
            else ans+=sum*(sum-1)/2,sum=1;
        ans+=sum*(sum-1)/2;sum=1;
        sort(b+1,b+1+pos,cmp);
        for(int i=2;i<=pos;i++)
            if(b[i].h==b[i-1].h)sum++;
            else ans+=sum*(sum-1)/2,sum=1;
        ans+=sum*(sum-1)/2;
        printf("%lld\n",ans);
    }
    return 0;
}


總結

希望以後成爲所有題都能補的強者!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章