下午打了一场练习赛,从做D题来看读题不认真,复杂代码实现起来还是太慢。E题我的思路并不够灵活。一直纠结于DE两题,其实F题和H题都应该做出来的。 还有英语QAQ。
整理下今天的练习赛。
ZOJ—3939
题意:当周一是在一个月的1,11,21日时被称为幸运日。给出一个日期是第一个幸运日,求第k个幸运日的日期。
因为数据量k<=1e9,所以必定是打表找规律。
这个题打下表就能发现400年一循环的规律。
代码来自:https://blog.csdn.net/RaAlGhul/article/details/51237029
typedef long long ll;
struct point
{
int year, month, day;
int cnt;
point (int year = 0, int month = 0, int day = 0, int cnt = 0): year(year), month(month), day(day), cnt(cnt){}
};
point a[10000];
int n;
int cnt;
int d1[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int d2[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool check(int x)
{
if (x % 400 == 0 || (x % 4 == 0 && x % 100 != 0)) return true;
return false;
}
void init()
{
cnt = 1;
int day = 1;
for (int i = 1753; i <= 1753 + 399; i++)
{
bool flag = false;
flag = check(i);
for (int j = 1; j <= 12; j++){
int tmp = (flag == 1 ? d2[j] : d1[j]);
while (day <= tmp){
if (day == 1 || day == 11 || day == 21)
{
a[cnt] = point(i, j, day, cnt);
cnt++;
}
day += 7;
}
day -= tmp;
}
}
}
int main()
{
init();
cnt--;
int t;
scanf("%d", &t);
while (t--){
int y, m, d;
scanf("%d%d%d", &y, &m ,&d);
int n;
scanf("%d", &n);
n--;
int ind = n / cnt;
int tmp = n - ind * cnt;
int mid = 0;
while (y >= 1753 + 400)
{
y -= 400;
mid++;
}
//printf("y = %d mid = %d tmp = %d\n", y, mid, tmp);
ll t1 = 0, t2 = 0, t3 = 0;
for (int i = 1; i <= cnt; i++){
if (y == a[i].year && m == a[i].month && d == a[i].day){
int g = i;
g += tmp;
while (g > cnt){
g -= cnt;
mid++;
}
t1 = (ll)a[g].year + (ll)(mid + ind) * 400;
t2 = (ll)a[g].month;
t3 = (ll)a[g].day;
break;
}
}
printf("%lld %lld %lld\n", t1, t2, t3);
}
return 0;
}
zoj3940—Modulo Query
题意:
F(1, X) = X mod A1.
F(i, X) = F(i - 1, X) mod Ai (2 ≤i≤N)
求F(N,X)=y,给出y,求有多少个X(X<=m)满足F(N,X)=y。
其实这道题就是求((X mod a1)mod a2)...)mod an=y。求X有多少个X(X<=M)满足条件。
这道题思路绝对是脑洞清奇。开拓思路。就是0到M这每个区间进行叠加,比如(1,M)的区间上所有数对a1取模,所得数必定在(0,a1-1)之间,下面可以快速算出大约有多少个区间变成了(0,a1-1),继续依次取模a2...。
这是官方题解:
可以观察到一个区间对一个数取模后的结果也可以用区间来表示,并且这些区间的左端点都是0。于是直接用一个map存每次取模之后的区间和这个区间出现次数,要取模的时候,找出所有右端点大于等于当前模数的所有区间,暴力算一下结果即可。对于查询,二分下位置,求个后缀和就好了。下面分析下复杂度为什么是对的。
众所周知:一个数对一堆数取模,最多会有log
次值的改变。对于这题每对一个新数取模,最多只会增加一个区间。考虑当前区间是[0,vi]
,要对x
取模,那么对于vi<x
的区间没有变化,对于vi≥x
的那些区间会变成[0,vi mod x]
,并且可能会新增区间[0,x−1]
。这个过程其实相当于每个ai
对后面所有数依次取模,那么总共会有O(nlogn)
次值的改变,每次改变需要在map上修改一下,总复杂度是O(nlog2n)
的。
代码:https://blog.csdn.net/below_crusder/article/details/51277936
#define maxn 500005
const int mod = 1e9+7;
int sum[maxn];
int ans[maxn];
struct node// 区间[0, right]以及该区间出现的次数cnt
{
int right, cnt;
node(int a = 0, int b = 0)
{
right = a,cnt = b;
}
bool operator<(const node &a) const
{
return right < a.right;
}
}tp[100005];
int main()
{
int t;
cin>>t;
while(t--)
{
int n, m, s;
cin>>n>>m;
priority_queue<node> que; //优先队列
que.push(node(m, 1)); //入队
while(n--)
{
cin>>s;
while(que.top().right>=s) //将区间右断点大于s的区间进行mod处理
{
node tmp=que.top();
que.pop();
while(!que.empty()&&que.top().right==tmp.right) //如果有重复的区间进行整合
tmp.cnt+=que.top().cnt,que.pop();
que.push(node(s-1,(tmp.right+1)/s*tmp.cnt)); //将处理过后的区间入队
if((tmp.right + 1)% s)
que.push(node(tmp.right%s, tmp.cnt)); //如果一个区间不能被整除 将多余的区间入队
}
}
int cnt = 1;
while(!que.empty()) //将最后得到的所有区间存在一个数组中
{
node tmp = que.top();
que.pop();
while(!que.empty() && que.top().right == tmp.right)
tmp.cnt += que.top().cnt, que.pop();
tp[cnt++]=tmp;
}
sort(tp+1,tp+cnt); //将各个区间进行从小到大排序
memset(sum, 0, sizeof sum); //初始化为0
for(int i = 1; i < cnt; i++)
{
ans[i] = tp[i].right;
sum[cnt - i] = sum[cnt - i + 1] + tp[cnt - i].cnt;
}
int q;
cin>>q;
long long out=0;
for(int i=1;i<=q;i++)
{
cin>>s;
int pos=lower_bound(ans+1,ans+cnt,s)-ans; //二分查找结果
out=(out+1ll*i*sum[pos]%mod)%mod;
}
cout<<out<<endl;
}
return 0;
}
Zoj—3941
题意:给出k是持续兴奋的天数,给出若干个区间作为可以选择的开始点,求怎么样选可以兴奋总天数最多。
思路1:先合并区间,从第一个区间的第一个点开始放,放1,k+1,,当下一个点放的区间右端在区间中不用管,继续向后放。如果不在,记录一下如果放可以得到多少。放完以后如果还有剩余在把存储的依次大到小拿出来。
思路2:当遇见右端点不在区间中时,dfs两个方向,放前一个的,放后一个的左端。
推荐博客:https://blog.csdn.net/snowy_smile/article/details/51253003
Zoj—3944
就是大暴力,暴力找该点不是“.”的就消一下周围满足条件的。