在CH上找了套题…
60滚粗啦!
T1的暴力懒得写,T3的提答好恶心…嘛就算都写上也就100分左右…我不会告诉你最后一个半小时左右我弃赛了…
wys是TKD的妹子。
wys听说TKD总把题面写得很长很长;
于是这一次她要求TKD把题面写得很短很短。
统计N个点N条边的简单无向连通图个数。
一行一个整数N。
输出一行一个整数,描述答案。
3
1
4
15
对于30%的数据:1<=N<=10;
对于60%的数据:1<=N<=50;
对于100%的数据:1<=N<=5000
暴力?
我想了这么几种暴力的写法:
1.先生成一棵树,然后再枚举第n条边怎么连,然后把环缩了,然后跑一遍prufer序列,然后判重…
2.枚举环的大小,然后再在环上的点上挂树
…算了我说不下去了…
总之…丧心病狂…
30分算法:
暴力+打表
60分算法:
用prufer序列知道有根树的大小是 nn−1 ,然后枚举环的大小,组合数瞎搞…然而并不会瞎搞…
100分算法:
公式:
枚举环的大小k:
前两个是树的个数:现在只有n-k个节点可以选,但这n-k个节点要从n个节点中选,一共是 nn−k−1 棵有根树,还要乘k,表示挂在k个节点上。
后两个是环的个数:一个序列的全排列是 k! ,因为环是首尾相接的,重复了k次,所以是 (k−1)! 个。环可以翻转,所以要除以2。从n个节点中选出k个节点,要乘以组合数。
然后这式子化简一下就是…
好吧我弱我只能推出来n>3的,剩下的只能打表了
不推的话会因为高精T掉…别问我怎么知道的
高精除不能写高精除高精,会T,写高精除int,别问我怎么知道的
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long LL;
const int SZ = 5010;
const int INF = 1000000010;
const int BASE = 1000000000;
const int WIDTH = 9;
struct bign{
int num[3000],len;
bign() { memset(num,0,sizeof(num)); len = 1;}
bign (LL x)
{
memset(num,0,sizeof(num)); len = 0;
do{
num[++ len] = x % BASE;
x /= BASE;
}while(x);
}
};
bool operator <(const bign &a,const bign &b)
{
if(a.len != b.len) return a.len < b.len;
for(int i = a.len;i >= 1;i --)
if(a.num[i] != b.num[i])
return a.num[i] < b.num[i];
return false;
}
bign operator +(const bign &a,const bign &b)
{
bign ans;
int i = 1,x = 0;
while(i <= a.len || i <= b.len)
{
x += a.num[i] + b.num[i];
ans.num[i ++] = x % BASE;
x /= BASE;
}
ans.num[i] = x;
ans.len = i;
while(ans.len > 1 && !ans.num[ans.len]) ans.len --;
return ans;
}
bign operator -(const bign &a,const bign &b)
{
bign ans;
ans.len = a.len;
int x = 0;
for(int i = 1;i <= a.len;i ++)
{
x = x + a.num[i] - b.num[i] + BASE;
ans.num[i] = x % BASE;
x = x / BASE - 1;
}
while(ans.len > 1 && !ans.num[ans.len]) ans.len --;
return ans;
}
bign operator *(const bign &a,const bign &b)
{
bign ans;
ans.len = a.len + b.len;
for(int i = 1;i <= a.len;i ++)
{
LL x = 0;
for(int j = 1;j <= b.len;j ++)
{
x = x + (LL)a.num[i] * b.num[j] + ans.num[i + j - 1];
ans.num[i + j - 1] = x % BASE;
x /= BASE;
}
ans.num[i + b.len] = x;
}
while(ans.len > 1 && !ans.num[ans.len]) ans.len --;
return ans;
}
bign operator /(const bign &a,const int &b)
{
bign ans;
ans.len = a.len;
LL x = 0;
for(int i = ans.len;i >= 1;i --)
{
x = x * BASE + a.num[i];
ans.num[i] = x / b;
x %= b;
}
while(ans.len > 1 && !ans.num[ans.len]) ans.len --;
return ans;
}
void print(const bign &ans)
{
printf("%d",ans.num[ans.len]);
for(int i = ans.len - 1;i >= 1;i --)
printf("%09d",ans.num[i]);
}
bign fac[SZ];
int main()
{
int n;
scanf("%d",&n);
if(n <= 2) { puts("0"); return 0; }
if(n == 3) { puts("1"); return 0; }
bign tmp = 1,ans;
for(int i = 1;i <= n - 1;i ++)
tmp = tmp * i; //(n - 1)!
ans = tmp;
tmp = tmp * n; //n!
ans = ans + tmp;//(n - 1)! * n!
for(int i = 1;i <= n - 4;i ++)
{
tmp = tmp * n / (i + 1);
ans = ans + tmp;
}
print(ans / 2);
return 0;
}
/* 4999 */
wys是TKD的妹子。
wys听说TKD总把题面写得很长很长;
于是这一次她要求TKD把题面写得很短很短。
给定一排n个柱子,第i个位置的高度为a_i
现在有m个操作,每次选择一个位置,将这个位置的高度减掉1
所有操作之前以及每次操作后输出当前的最大子矩形的大小
强制在线
第一行两个正整数n,m,含义如题目中所示
接下来一行n个非负整数,第i个数a_i表示第i个位置的高度
接下来m行每行一个非负整数pos_i,代表将位置pos_i\oplus last_ans的柱子高度-1
last_ans表示上一次询问的答案,初始值为一开始的最大子矩形大小
m+1行,每行一个正整数,代表第i-1次操作后的最大子矩形大小
7 3
2 2 4 5 3 3 3
10
12
15
15
14
10
8
对于30%的数据:1\leq n,m\leq 1000
对于另外20%的数据:1\leq n,m\leq10^5,a_i\leq 1
对于100%的数据:1\leq n,m\leq10^5,a_i\leq10^6
数据保证任何时刻所有位置的高度\geq 0
四次询问的答案如下图所示:
原创
请注意输入和输出可能超过32位整型范围
//CH的图片地址好诡异…
三十分单调栈+二十分线段树,普及组都拿得到的分数
然后我不会做了,看不懂题解,弃疗
直接粘官方题解:
【100 分做法 1】
考虑将 50 分做法中的线段树改为可持久化线段树,每行维护一个
再用一个 set 维护一下所有行的最大连续区间*高度的最大值
由于每次修改只会修改一个位置,因此时空复杂度都是
O((n+m)log2h+h) 的
【100 分做法 2】
考虑将所有的连续最大子区间缩成一个 pair< int,int >扔进对应行的 set 里
这样就不需要可持久化线段树了
时间复杂度
O((n+m)log2h+h) ,空间复杂度 O(n+h)
【P.S.】
如果 h 很大怎么办?
由于有用的行数只有 n+m 行,离散化一下就好了 0.0
50分代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long LL;
const int SZ = 1000010;
const int INF = 1000000010;
const double eps = 1e-6;
int n,m;
LL num[SZ];
struct haha{
LL w,h;
}S[SZ];
int top = 0;
LL getans()
{
LL ans = 0;
for(int i = 1;i <= n;i ++)
{
LL d = 0;
while(top && S[top].h > num[i])
d += S[top].w,ans = max(ans,S[top].h * d),top --;
S[++ top] = (haha){d + 1,num[i]};
}
LL d = 0;
while(top)
d += S[top].w,ans = max(ans,S[top].h * d),top --;
return ans;
}
struct segment{
int l,r;
LL lx,rx,sum,mx;
}tree[SZ << 2];
void update(int p)
{
int lch = p << 1,rch = p << 1 | 1;
tree[p].sum = tree[lch].sum + tree[rch].sum;
tree[p].lx = max(tree[lch].lx,tree[lch].sum + tree[rch].lx);
tree[p].rx = max(tree[rch].rx,tree[rch].sum + tree[lch].rx);
tree[p].mx = max(max(tree[lch].mx,tree[rch].mx),tree[lch].rx + tree[rch].lx);
}
void build(int p,int l,int r)
{
tree[p].l = l; tree[p].r = r;
if(l == r)
{
tree[p].lx = tree[p].rx = max(0ll,num[l]);
tree[p].mx = tree[p].sum = num[l];
return ;
}
int mid = l + r >> 1;
build(p << 1,l,mid);
build(p << 1 | 1,mid + 1,r);
update(p);
}
void change(int p,int pos,LL x)
{
if(tree[p].l == tree[p].r)
{
tree[p].lx = tree[p].rx = max(0ll,x);
tree[p].mx = tree[p].sum = x;
return ;
}
int mid = (tree[p].l + tree[p].r) >> 1;
if(pos <= mid) change(p << 1,pos,x);
else change(p << 1 | 1,pos,x);
update(p);
}
int main()
{
// freopen("T2.in","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%d",&n,&m);
bool flag = 0;
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&num[i]);
if(num[i] > 1)
flag = 1;
}
if(!flag)
{
for(int i = 1;i <= n;i ++)
if(num[i] == 0) num[i] = -INF;
build(1,1,n);
LL lastans = tree[1].mx;
printf("%lld\n",lastans);
while(m --)
{
LL pos;
scanf("%d",&pos);
pos ^= lastans;
change(1,pos,-INF);
lastans = tree[1].mx == -INF ? 0 : tree[1].mx;
printf("%lld\n",lastans);
}
}
else
{
LL lastans = getans();
printf("%lld\n",lastans);
while(m --)
{
LL pos;
scanf("%lld",&pos);
pos ^= lastans;
num[pos] --;
lastans = getans();
printf("%lld\n",lastans);
}
}
return 0;
}
/* */
wys是TKD的妹子。
wys听说TKD总把题面写得很长很长;
于是这一次她要求TKD把题面写得很短很短。
这次的题面是有故事的>w<
有一天,TKD给wys发了一份密码,然后就默默地去码题了;
wys当然很不理解啊,发一串密码是个啥玩意儿;
然后呢,TKD很良心地给wys发了一份解密程序;
于是wys就能成功看懂了TKD发的东西了!
但是wys还是不满足,
她还想知道TKD是怎样把字符串加密的,
wys发现自己想不出来,所以想请你来帮帮她的忙。
帮wys写出加密程序的话,wys是会给你奖励的哦~
第一行一个整数表示数据编号
接下来四行表示每一组数据的四个字符串。
第一行一个整数表示数据编号
接下来四行表示每一组数据加密之后的密文。
TKD一共有五种加密方法,每种加密方法有10组数据。
wys已经收集到了全部的字符串,但是密文只收集到了一半。
即给选手的明文和输入文件完全相同(5行),密文只有输出文件中密文的一半(3行)。
提示
密文的格式是由若干个01串组成的;
01串的长度固定为8,两个01串之间用一个空格隔开;
每一个01串代表一个二进制数字;
也就是说这5种加密都是字符串→数字串的加密方式;
因为8位01串代表的是0~255之间的数,所以也可以理解为字符串→字符串的加密方式。
另外输入的字符串全部由字母数字空格以及常用字符&标点符号组成,不包括\0和\n。
对于给出的加密程序模板,
保证只会调用一次Srand();
保证不会调用无用的Rand();
不保证Len不变;
不保证交换两行输入字符串的顺序,得到的密文和原来一样;
即一个字符串对应的密文可能和上一个字符串有关;
并且仅可能和自己以及上一个字符串有关。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void _gets(char *UseS)
{
gets(UseS);
static int Len_of_U;
Len_of_U=strlen(UseS);
if(UseS[Len_of_U-1]=='\r')
UseS[Len_of_U-1]=0;
}
namespace Random_Number
{
unsigned Seed;
void Srand(unsigned GetSeed)
{
Seed=GetSeed;
}
unsigned Rand(void)
{
Seed=Seed*1103515245+12345;
return (Seed/65536)%32768;
}
}
namespace Case0
{
unsigned char GetC[_L];
void Print(unsigned char Out)
{
static bool Save[8];
for(int i=0;i<8;i++)
{
if(Out&1)
Save[i]=true;
else
Save[i]=false;
Out>>=1;
}
for(int i=7;i>=0;i--)
if(Save[i])
printf("1");
else
printf("0");
}
void Main()
{
static int Len;
for(int Now=0;Now<4;Now++)
{
_gets((char*)GetC);
Len=strlen((char*)GetC);
...
for(int i=0;i<Len;i++)
Print(GetC[i]),printf("%c",i^(Len-1)?' ':'\n');
}
}
}
namespace Case1
{
unsigned char GetC[_L];
void Print(unsigned char Out)
{
static bool Save[8];
for(int i=0;i<8;i++)
{
if(Out&1)
Save[i]=true;
else
Save[i]=false;
Out>>=1;
}
for(int i=7;i>=0;i--)
if(Save[i])
printf("1");
else
printf("0");
}
void Main()
{
static int Len;
for(int Now=0;Now<4;Now++)
{
_gets((char*)GetC);
Len=strlen((char*)GetC);
...
for(int i=0;i<Len;i++)
Print(GetC[i]),printf("%c",i^(Len-1)?' ':'\n');
}
}
}
namespace Case2
{
unsigned char GetC[_L];
void Print(unsigned char Out)
{
static bool Save[8];
for(int i=0;i<8;i++)
{
if(Out&1)
Save[i]=true;
else
Save[i]=false;
Out>>=1;
}
for(int i=7;i>=0;i--)
if(Save[i])
printf("1");
else
printf("0");
}
void Main()
{
static int Len;
for(int Now=0;Now<4;Now++)
{
_gets((char*)GetC);
Len=strlen((char*)GetC);
...
for(int i=0;i<Len;i++)
Print(GetC[i]),printf("%c",i^(Len-1)?' ':'\n');
}
}
}
namespace Case3
{
unsigned char GetC[_L];
void Print(unsigned char Out)
{
static bool Save[8];
for(int i=0;i<8;i++)
{
if(Out&1)
Save[i]=true;
else
Save[i]=false;
Out>>=1;
}
for(int i=7;i>=0;i--)
if(Save[i])
printf("1");
else
printf("0");
}
void Main()
{
static int Len;
for(int Now=0;Now<4;Now++)
{
_gets((char*)GetC);
Len=strlen((char*)GetC);
...
for(int i=0;i<Len;i++)
Print(GetC[i]),printf("%c",i^(Len-1)?' ':'\n');
}
}
}
namespace Case4
{
unsigned char GetC[_L];
void Print(unsigned char Out)
{
static bool Save[8];
for(int i=0;i<8;i++)
{
if(Out&1)
Save[i]=true;
else
Save[i]=false;
Out>>=1;
}
for(int i=7;i>=0;i--)
if(Save[i])
printf("1");
else
printf("0");
}
void Main()
{
static int Len;
for(int Now=0;Now<4;Now++)
{
_gets((char*)GetC);
Len=strlen((char*)GetC);
...
for(int i=0;i<Len;i++)
Print(GetC[i]),printf("%c",i^(Len-1)?' ':'\n');
}
}
}
int main()
{
int Case;
Random_Number::Srand(1);
scanf("%d",&Case);
static char Empty[10];
gets(Empty);
printf("%d\n",Case);
Case/=10;
if(Case==0)
Case0::Main();
if(Case==1)
Case1::Main();
if(Case==2)
Case2::Main();
if(Case==3)
Case3::Main();
if(Case==4)
Case4::Main();
return 0;
}
样例下载密码: ezv1
数据下载密码: c5y8
很容易看出来二十分是字符和数字一一对应的…只要抠出来就行了…好吧我也就到这个水平了233
直接粘官方题解:
首先我要自豪地说一句:
‘这是一道 wys 都只能拿 60 分的题! ’
并没有部分分算法
我们来分析一下加密算法吧。 。 。
首先拿到第一种加密方法,你可以发现:
‘这 NM 不就是一 SB 映射吗?’
然后要么打表,要么找到我是怎么随机全排列的,你就可以 A 掉了。
再看第二种加密,你可以稍微试试;
第一个数加密之后增加了一个定值,第二个数也同样。 。 。
这不就是把每个数都加了一个 Rand 吗?
一对比可以发现,的确加密就是直接顺着加 Rand。
第三种加密:
233 对应的密文有一个 0,2333 对应的密文有两个 0。
貌似只要做差就可以了。 。 。
然而第一个字母怎么做差?
观察一下,每次只用加一个 Rand 就好咯~
第四种加密:
啊这货有点难呢。 。 。 。然后你多试了几遍。 。 。
发现。 。 。咦?貌似对于每一位,结果都是一一对应的啊。 。 。
然后渐渐又能发现。 。 。对于每一位,明文每加 1,密文就会加一个定值。
然而那个定值只会是 3 or 53 or 101。
所以大胆猜测,加密方法为 X=X*P[Rand()%3]+Rand();
P 为{3,53,101}。
然后你就发现你猜对了。
第五种加密:
咦?密文都是一样长的?
那么长度一定的序列会是什么呢?
prufer 序列!
对了就是这货~
我们首先把输入的串的长度增长。 。 。增长到 254
然后再把这个序列看成一个 256 个节点的树的 prufer 序列
我们把它还原
假设根是 0,输出除了 0 之外所有节点的父亲。
我们就可以得到密文了~
这尼玛能做?…